这篇文章讲解prototype,__proto__和prototype chain三者到底是什么以及三者之间的关系。我们先来看一段代码:
function Dog() {}Dog.prototype.legsCount = 4;Dog.prototype.bark = function () { console.log('wang, wang, wang');};let dog = new Dog();console.log(dog.bark()); //'wang, wang, wang'console.dir(Dog);console.dir(dog);
最后两行代码的打印结果为:
我们打印了Dog,它是一个function;我们打印了dog,它是一个object。首先我们看到:
1: prototype本身是一个对象字面量类型的object,__proto__也是一个object2: prototype是只存在与function类型的属性,对象并没有prototype属性3: 对象dog的__proto__的内容就是其构造函数Dog()的prototype的内容
我们可以看到MDN关于__proto__的解释为:
当实例化一个对象的时候,__proto__用来指向一个作为prototype的对象。也就是说__proto__指向的对象就是构造函数的prototype属性。
这也是为什么我们调用dog.bark()可以成功的原因。因为当我们调用dog.bark()的时候,会先在this上去寻找bark()方法,但是我们的构造函数Dog()内部并没有定义bark()方法。这时候,正是因为dog对象有__proto__属性,所以沿着__proto__找到Dog.prototype,从而最终找到了bark()方法。
prototype chain
我们知道JavaScript的继承是基于原型的继承。而每一个对象都有一个__proto__属性,它指向其构造函数的prototype属性,而prototype本身也是一个对象,它也有自己的__proto__属性,而这个__proto__属性又指向。。。。,所以这样层曾上源,就形成了一个类似链表的关系,这个关系就是我们的prototype chain也就是原型链。
所有对象原型链的终点都是Object.prototype, 最终指向null。
原型链的特点有哪些?
1: 实例化对象之后再修改构造函数的prototype属性
function Dog() {}Dog.prototype.legsCount = 4;Dog.prototype.bark = function () { console.log('wang, wang, wang');};let dog = new Dog();console.log(dog.legsCount); //4//修改prototypeDog.prototype = { legsCount: 3};console.log(dog.legsCount); //4
以一个新对象的方式重写prototype之后,已经实例化的对象的__proto__并不会改变,因为它不会再重新指向一个新的对象。
以一个新对象的方式重写prototype这种方式也还是危险的。因为prototype作为每个函数的默认属性,它本身还有一个constructor属性prototype.constructor。以一个新对象的方式重写prototype,会抹去prototype.constructor属性。
2: 当原型链上的属性为对象类型时,会造成数据污染
function Dog() {}Dog.prototype.color = ['black', 'white'];Dog.prototype.bark = function () { console.log('wang, wang, wang');};let dog1 = new Dog();let dog2 = new Dog();console.log(dog1.color);// ["black", "white"]console.log(dog2.color);// ["black", "white"]dog1.color.push('red');console.log(dog1.color);// ["black", "white", "red"]console.log(dog2.color);// ["black", "white", "red"]
在这种情况下,当我们改变其中一个对象的属性,其他所有的实例对象都会受影响。