你真的懂原型么?
基本看过高程等书的人都可以对原型继承,原型链查找侃侃而谈,代码中也可以使用原型完成一些事情,但是,我们对于原型真的彻底搞明白了么?
原型由构造器确定
我们的原型是一个对象,构造器函数有一个属性指向这个对象,prototype;而我们每次new出来的实例也有一个属性指向这个对象,__proto__。为什么说原型是由构造器确定的,因为在new之后,这个实例的原型就已经确定了,实例的__proto__和构造器的prototype都会指向那个原型对象。
var Person=function(){};
var p=new Person();
console.log(p.__proto__===Person.prototype); // true
Person.prototype={
say:function(){
console.log("hello");
}
}
var t=new Person();
console.log(t.__proto__===Person.prototype); // true
t.say(); // hello
console.log(p.__proto__===Person.prototype); // false
p.say(); // 报错
这段代码就很好的说明了这个问题。实例p通过构造器函数Person创建出来(new)之后,它的__proto__和Person的prototype指向同一个对象,所以console的时候相等。然后,我们改变一下构造器函数Person的原型指向,并创建了一个实例t,我们发现t的__proto__和Person的prototype的指向依旧是一致的,但是因为Person.prototype已经改变了,而p.__proto__依旧指向原来的原型对象,所以p.__proto__和现在的Person.prototype指向的对象并不是同一个,会返回false。证据就是p.say()会报错,但是t.say()可以返回正确的值。
原型链查找,是通过实例的属性
看下面的代码:
var Person=function(){};
var p=new Person();
Person.prototype.say=function(){
console.log("1");
}
p.say(); //1
我们先创建了实例,再给原型添加方法,最后调用时,依旧可以返回正确结果,这说明p.say()是一个查找的过程,它会去原型对象里查找相应的方法,如果有返回结果,没有就会报错。
那么,既然是通过实例调用的方法,自然也是通过实例的__proto__属性去访问到那个原型对象,如何证明?我们看下面的代码:
var Person=function(){};
var p=new Person();
Person.prototype.say=function(){
console.log(1);
}
p.say(); //1
p.__proto__={
say:function(){
console.log(2);
}
}
p.say(); //2
当我们重写p.__proto__之后,相当于p这个实例的原型就是我们重写的这个对象。无论我们对Person.prototype添加任何方法,p这个实例都访问不到这些方法,因为原型链查找通过的是__proto__属性,而现在,p.__proto__和Person.prototype指向的其实是不同的两个对象了。
原型是客观存在的
当我们new了一个实例之后,原型对象就已经存在了,实例的__proto__和构造器的prototype都会指向它。而创建实例之后,我们如果修改了__proto__或者prototype,其实都是在修改那个唯一的原型对象。要是我们重写了__proto__或者prototype,原型对象依旧存在,但是通过__proto__或者prototype只能访问到重写的那个对象。所以我们一直在强调,new是一个分界线,new的过程就是把实例的__proto__指向构造器的prototype所指向的那个对象。
看点例子:
var Person=function(){};
var p=new Person();
var t=new Person();
p.__proto__.say=function(){
console.log(1);
}
p.say(); //1
t.say(); //1
p.__proto__={
say:function(){
console.log(2);
}
}
p.say(); //2
t.say(); //1
Person.prototype={
say:function(){
console.log(3);
}
}
var x=new Person();
p.say(); //2
t.say(); //1
x.say(); //3
如果你对上述代码没有任何疑问,那么,你应该是真的懂了原型。