重新理解原型链
我现在认为之前之所以对原型链混淆以及莫名其妙根本原因在于我一开始就没有搞清楚到底什么是原型。
之前也看过不少其他人写的关于原型链的解释,但是越看越糊涂。
我现在觉得要想搞清楚这个问题应该从0开始,先从__proto__开始。
看一下下面分析过程
对象数据实际是跟着一个__proto__属性的。而实际中使用对象自身的一些方法也是通过__proto__实现的。
不信用Object.create(null),创建一个对象,发现没有了__proto__这个属性。也不能使用对象的方法。这就证明对象的确是通过__proto__使用自身方法的。
然后再看,发现函数既具备__proto__还具备prototype属性。
const test = function(){ } console.log('test.__proto__:', test.__proto__,'test.prototype:',test.prototype)
这就有意思了。我见过一些文章,有人把对象的继承也叫原型链,但是对象是没有prototype这个属性的,叫原型链合适吗?姑且不深究这个对错。
再看下面的测试,将函数的prototype属性赋值为null(实际只是改变了引用对象)。发现函数依然使用对象的一些方法,那么实际是函数也是借助__proto__拿到对象的方法的,如果再将__proto__赋值为null,发现就拿不到了。
const test = function(){ } test.prototype = null console.log('test.toString:', test.toString()) test.__proto__ = null console.log('test.toString:', test.toString())
也就制造了一种Object.create(null)的效果。
上面是实际的现象。
再回顾一下prototype的作用,它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。
然后将普通函数变为构造函数,用new 处理一下。然后在函数的prototype上面写一些公共的属性,结果每个实例化的对象都能访问到。
然后当原型上和实例化对象自身拥有同一个属性时候,以自身的为终值,而不是原型覆盖自身。这就证明了先是查找__proto__,在查找构造函数的原型。
分析到此其实已经很明显了:如果说原型指的就是prototype这个属性的话,那么只有函数才有这个属性。
而对象分为两种:一种是通过构造函数创建,一种不是。通过构造函数创建的对象在其__proto__链条上有若干个是属于其构造函数的prototype的。而这也就是为什么实例化的对象能够访问到父辈原型属性的原因。同时构造函数的"prototype === 实例化对象的__proto__"。如果自己写的话就是prototype的值和__proto__是同一个引用对象。所以改了prototype 也就改了__proto__,实际还是利用了__proto__。
总结一下:所谓的原型链是通过__proto__实现的。是JavaScript处理继承关系的一种机制,会将构造函数原型中属性值作为构造的下一个__proto__节点的值。因为__proto__的回溯机制也就能访问到了。
这样也符合构造函数自身没法直接访问原型上的属性。原型上的属性只对实例化的对象才有意义。
至于new操作符干的事情很好的印证了上面。new 操作符做的就是创建一个空对象,没有原型链的空对象,然后将对象的__proto__指向构造函数的prototype,然后将空对象赋值给构造函数的this,然后执行构造函数的逻辑。最后返回这个对象。
注意:有人把__proto__叫做原型,这样确实是可以叫原型链了,__proto__是链接的key,其值为链接的值,值上面又有__proto__。prototype也可以理解为实例化对象__proto__的一个引用,方面去使用。
我站在山顶看风景!下面是我的家乡!