Fork me on GitHub

返璞归真篇之原型链三剑客

 最近在做一些API的自实现,然后发现做到原型链相关的内容的时候还是底子不扎实,所以决定做一篇文章梳理一下。我们常说的原型链内容,不过是对__proto__prototypeconstructor这三者的理解罢了。

  我们先从这2个看上去很相似的内容开始讨论: __proto__prototype有什么联系?

  __proto__是对象所独有的属性而prototype是函数所独有的属性,但是我们同时也知道在JS中函数也是对象的一种,所以函数同时具有__proto__以及prototype两个属性,而对象则不一定会有prototype属性。

  __proto__属性是做什么的呢?它是对其他对象的引用,这个其他对象就是我们所说的原型对象。我们就是通过这个属性来链接整一条原型链的,我们常见的当前对象中找不到某属性,顺着原型链往上找就是它的功劳,而一般来说这些原型链的尽头都会指向Object.prototype,再往上则是null,这里就到了原型链的顶部了,网上有张图我觉得还不错,比较直白地描绘了一般的FunctionObject的原型链接:

  那prototype又是什么含义?举个例子,我们有一个Foo函数:function Foo(){},当我们对其进行new构造调用时,Fooprototype属性会被进行关联,关联的是它创建的实例对象的__proto____proto__也可以通过Object.getPrototypeOf获取,所以有以下等式:

1
2
3
4
function Foo() {}
let f1 = new Foo();
f1.__proto__ === Foo.prototype; // true
Object.getPrototypeOf(f1) === Foo.prototype; // true

  继续借用上面的例子,我们说一下最后的constructorconstructor同样是对象所独有的,最基本的含义就是指向该对象的构造函数,所以我们可以很自然的得到:

1
2
3
f1.constructor === Foo; // true
Foo.prototype.constructor === Foo; // true
f1.__proto__.constructor === Foo; //true

  以上我们似乎可以很自然得到f1实例对象本身存在一个指向Fooconstructor属性,然而实际上它是顺着原型链在Foo.prototype上“继承”了这个属性,自身是没有的,并且Foo.prototypeconstructor属性是在Foo声明时的默认属性;综上,才有了f1.constructor === Foo以及f1.__proto__.constructor === Foo成立。

  由此可见,Foo.prototype这个原型对象不能乱赋值;举个例子,如果我在前文的代码里写入Foo.prototype = {},那f1constructor就不再是Foo了,而是Object

1
2
3
4
5
function Foo() {}
Foo.prototype = {};
let f1 = new Foo();
f1.construtor === Foo; // false
f1.construtor === Object; // true

  因为虽然f1__proto__还是挂在Foo.prototype身上,但是Foo.prototype现在被赋值成了{}{}等价于new Object(),它是Object构造的实例对象,所以它的原型链(实例对象.__proto__)上的constructorObject,故最终f1constructor指向Object

1
2
3
let obj = {};
obj.__proto__ === Object.prototype; // true
Object.prototype.constructor === Object; //true

  也许前面说的不是很清晰,但是推导思路是没问题的,下面上一张网图(实在懒得画),相信看完就懂了: