Javascript prototype 原型链,看这一篇就够了

JS本身不是面向对象语言,并没有类的支持。这不妨碍很多人对面向对象的热爱。因此,在ES2015之前,我们经常看到这样创建实例:

function Pet(species) {
  this.species = species;
}
Pet.prototype.getSpecies = function() { return this.species; }
var cat1 = new Pet('cat');
console.log(cat1.getSpecies());  // 结果:"cat"

再用prototype(原型)模拟继承:

function Cat(color) {
  this.color = color;
}
Cat.prototype = new Pet('cat');
Cat.prototype.getColor = function() { return this.color; }
var blackCat = new Cat('black');
console.log(blackCat.getSpecies());  // 结果:"cat"
console.log(blackCat.getColor());  // 结果:"black"

除此之外还有好几种方式可以模拟继承的操作,详情参见这篇有意思的博文:JavaScript常用八种继承方案。此外,MDN上也有4种方式以及它们之间的比较:继承与原型链

prototype原型

我们可以根据以上简单的栗子看出,所谓的原型也就是个对象。在浏览器控制台中打印一下Pet.prototype可以看到如下内容:

{getSpecies: ƒ, constructor: ƒ}

再打印一下Pet.prototype.constructor

ƒ Pet(species) {
  this.species = species;
}

原来在声明函数但没有指定继承对象的时候,Javascript创建了一个原型对象,并且把该函数充作了构造函数。

这个隐式属性是非实例对象才有的,不信我们打印一下cat1

Pet {species: "cat"}
	species: "cat"
	__proto__:
		getSpecies: ƒ ()
		constructor: ƒ Pet(species)
		__proto__: Object

另外除了函数(包括Object()Array()这些构造函数)、以及特殊值如undefined, null等其他值对象都可以考虑是某个构造函数的实例。

NaNnumber类型的,它实际上是Number()的实例。因此没有prototype属性:

console.log(NaN.prototype)  // 打印:undefined

__proto__属性

cat1实例里我们看到了在Pet.prototype的构造函数中添加的自身属性species,因为表明实例化过程中调用了构造函数。

此外,还有一个陌生的__proto__属性,而这个属性的值正是我们刚刚打印过的Pet.prototype。也就是说**实例对象有个__proto__属性,它指向实例对象的构造函数constructor的原型prototype对象。**不过注意这个__proto__其实已经被标准弃用多年,但许多浏览器仍在沿用的属性。注意现在标准推荐的是Object.getPrototypeOf()方法。

由此可以推断出子类blackCat__proto__属性值是Cat.prototype,即一个Pet实例。

然而这并不适用于所有对象,比如{}:

console.log(({}).__proto__ === ({}).__proto__.constructor.prototype);  // 打印:true
console.log(({}).__proto__ === ({}).__proto__.__proto__);	//打印:false
console.log(({}).__proto__.__proto__);	// 打印:null

关于这个null是怎么回事,让我们在下一部分“原型链”中解释。

原型链

你大概已经能猜到原型链是什么了——一个可以追溯对象原型的链表。

通过__proto__可以追溯构造函数的原型。浏览器便是利用__proto__追溯寻找一个属性的。让我们试试:

console.log(cat1.__proto__);
/*
{getSpecies: ƒ, constructor: ƒ}
getSpecies: ƒ ()
constructor: ƒ Pet(species)
__proto__: Object
*/

console.log(cat1.__proto__.__proto__);
/*
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
*/

console.log(cat1.__proto__.__proto__.__proto__);
/*
null
*/
console.log(cat1.__proto__.__proto__.__proto__);
/*
VM2565:1 Uncaught TypeError: Cannot read property '__proto__' of null
    at <anonymous>:1:36
*/

这就到头了。

首先我们可以看到从函数new出来的实例变成了Object类型,而Object()构造函数的原型是null原型链到null为止

__proto__null的对象,称为原子对象,也就是最小单位——Object

ArrayNumber这些都是由Function创建而来,而FunctionObject创建而来。这里不得不提到另外一个有趣的发现:

console.log(Function.__proto__.__proto__.__proto__);  // 打印: null
console.log(Function.__proto__.constructor);	// Chrome, Safari打印:ƒ Function() { [native code] }
console.log(Pet.__proto__.__proto__.__proto__); // 打印: null

这跟预想的不同。。。看起来我们直接能调用的Function已经被浏览器“调包”了。

一图总结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值