之前对原型链的理解处于表面上的:
一个实例化的对象,当访问它的某个属性时,如果它本生没有该属性,则会去原型找该属性,再没有再到原型的原型上找,直到最后一个原型。
表面上并没有问题,但是当我以这个理论写了Person.getName(),却被告诉Person.getName不是一个函数,也就是找不到getName方法;
代码:
function Person() {
this.name = 'woff';
}
Person.prototype.getName = function() {
return this.name;
}
Person.getName();
当Person.getName时,Person本身没有,它在到它的原型上找到getName,但事实不是如此。
但是如果new一个对象就没问题
let p1 = new Person();
p1.getName(); //'woff'
联想到之前看到的__proto__,以及相关资料,得出结论
原型链的继承是依赖对象的__proto__属性完成的,prototype属性只是为__proto__提供指向而已。
首先说说对象的__proto__属性,当创建一个对象时,该对象会有一个__proto__属性,指向Object对象,当该访问对象的某个属性时,如果它自己没有就会到__proto__所指向的对象上面找。
那p1是如何找到getName属性的呢,要看new操作发生了什么
当let p1 = new Person(),实际是创建了一个{},再调用Person.apply({}, arguments)。即对象冒充,在{}上创建了一个name属性。
此时,相当与执行了
p1 = Object.create(Person.prototype);
Person.call(p1);
最终将赋值给p1,但是该对象并没有getName属性,所以它会到__proto__属性上去找,之前说了当创建一个{}时,它的__proto__是指向Object的,但是getName并不在Object上,所以,如果让__proto__指向Person的原型对象,那不是就能找到getName了。
Person上有个prototype属性,每个被创建的函数都会有该属性,指向这个函数的原型对象,所以prototype的作用就是保存该原型对象的引用,在new一个对象时,将该引用传给{}的__proto__属性,prototype仅此而已,不做其他的事情了。
{}.__proto__ = Person.prototype, 正是这条语句让原型链串起来了。
所以一开始的Person.getName是不存在的,因为Person要找getName,是靠__proto__实现的,而Person是个函数,不存在该属性。而p1可以,因为p1是对象,并且__proto__指向Person.prototype。值得注意的是prototype仅仅只是保存原型的引用而已。
现在用一幅图来形容一条原型链的本质
这样的逻辑就很清晰了,还有要注意的是emily是没有prototype属性的,只有构造函数才有, 但是emily有__proto__属性,构造函数是不可能存在的。
避免操作对象的__proto__属性,因为这将改变原型链,即使是新增或删除属性,因为prototype是被所有的实例化对象所共有的,修改将会对所有的对象产生影响。如果要操作prototype,应该用构造函数,如Person.prototyoe.xxx = function() { xxx }。