最近在做一些API的自实现,然后发现做到原型链相关的内容的时候还是底子不扎实,所以决定做一篇文章梳理一下。我们常说的原型链内容,不过是对
__proto__,prototype,constructor这三者的理解罢了。
我们先从这2个看上去很相似的内容开始讨论: __proto__和prototype有什么联系?
__proto__是对象所独有的属性而prototype是函数所独有的属性,但是我们同时也知道在JS中函数也是对象的一种,所以函数同时具有__proto__以及prototype两个属性,而对象则不一定会有prototype属性。
__proto__属性是做什么的呢?它是对其他对象的引用,这个其他对象就是我们所说的原型对象。我们就是通过这个属性来链接整一条原型链的,我们常见的当前对象中找不到某属性,顺着原型链往上找就是它的功劳,而一般来说这些原型链的尽头都会指向Object.prototype,再往上则是null,这里就到了原型链的顶部了,网上有张图我觉得还不错,比较直白地描绘了一般的Function、Object的原型链接:

那prototype又是什么含义?举个例子,我们有一个Foo函数:function Foo(){},当我们对其进行new构造调用时,Foo的prototype属性会被进行关联,关联的是它创建的实例对象的__proto__,__proto__也可以通过Object.getPrototypeOf获取,所以有以下等式:
1 | function Foo() {} |
继续借用上面的例子,我们说一下最后的constructor,constructor同样是对象所独有的,最基本的含义就是指向该对象的构造函数,所以我们可以很自然的得到:
1 | f1.constructor === Foo; // true |
以上我们似乎可以很自然得到f1实例对象本身存在一个指向Foo的constructor属性,然而实际上它是顺着原型链在Foo.prototype上“继承”了这个属性,自身是没有的,并且Foo.prototype的constructor属性是在Foo声明时的默认属性;综上,才有了f1.constructor === Foo以及f1.__proto__.constructor === Foo成立。
由此可见,Foo.prototype这个原型对象不能乱赋值;举个例子,如果我在前文的代码里写入Foo.prototype = {},那f1的constructor就不再是Foo了,而是Object:
1 | function Foo() {} |
因为虽然f1的__proto__还是挂在Foo.prototype身上,但是Foo.prototype现在被赋值成了{},{}等价于new Object(),它是Object构造的实例对象,所以它的原型链(实例对象.__proto__)上的constructor是Object,故最终f1的constructor指向Object:
1 | let obj = {}; |
也许前面说的不是很清晰,但是推导思路是没问题的,下面上一张网图(实在懒得画),相信看完就懂了:

