配色: 字号:
javascript中prototype、constructor以及__proto__之间的三角关系
2016-11-02 | 阅:  转:  |  分享 
  
javascript中prototype、constructor以及__proto__之间的三角关系



三者暧昧关系简单整理



在javascript中,prototype、constructor以及__proto__之间有着“著名”的剪不断理还乱的三角关系,楼主就着自己对它们的浅显认识,来粗略地理理以备忘,有不对之处还望斧正。



楼主的一句话理解就是:某个对象的constructor属性返回该对象构造函数,其__proto__属性是个对象,值和其构造函数的prototype属性值一致。



先来说说prototype。prototype的解释是“原型”,js的所有函数都有一个prototype属性,其属性值是个对象,原型对象初始值是空的(其实还有constructor属性和__proto__属性,只是不能被枚举,可以参考最下面留言),代码验证:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



varnum=0;

for(variinPerson.prototype)

num++;



console.log(num);//0

复制代码

prorotype的主要作用简单来说就是“便于方法或者属性的重用”,可以利用prototype添加扩展属性和方法,举个简单的例子:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



Person.prototype.show=function(){

console.log(this.name);

};



Person.prototype.sex=''male'';



vara=newPerson();

console.log(a.sex);//male

a.show();//hanzichi

console.log(a.__proto__===Person.prototype);//true对象a的__proto__值取自构造函数Person的prototype值

console.log(a.constructor===Person);//true对象a的构造函数是Person

console.log(Person.prototype.constructor===Person);//true

复制代码

如上,所有用Person函数构造的对象都可以使用sex值,调用show方法。



那么问题来了,以上代码Person函数的prototype值是什么?我们尝试打印(console.log(Person.prototype)):







我们发现Person.prototype值确实是个对象,本身有两个属性(show和sex),这是自定义的,还有两个属性(constructor和__proto__可以通过hasOwnProperty验证),一个是constructor,值为函数本身,另一个就是__proto__(据说ie不支持楼主没测试),其值为父函数的prototype属性值。因为Person继承自Object,故其__proto__值为Object的prototype属性值,也就是说Person继承了Object本身的所有方法,我们可以展开来细看:







说完prototype,我们再来看看对象实例有哪些属性,我们也将它打印出来(console.log(a)):







我们看到实例对象a除了本身自带属性外,也有个属性__proto__。



我们通过a.show()调用了show方法,但是show方法并不显示在对象本身属性里(可通过hasOwnProperty验证),为何能用?又是__proto__!所有的实例对象都有个__proto__属性,我们看到它的值跟Person.prototype一致,也就是说实例a继承了Person类的属性方法。本身的属性里找不到show方法,自动去__proto__中寻找。



再说说constructor,其值返回该对象的构造函数:



复制代码

console.log(''string''.constructor);//functionString(){[nativecode]}

console.log(newString(''string'').constructor);//functionString(){[nativecode]}

console.log(/hello/.constructor);//functionRegExp(){[nativecode]}

console.log([1,2,3].constructor);//functionArray(){[nativecode]}

Functionwww.baiyuewang.netA(){};

vara=newA();

console.log(a.constructor);//functionA(){}

复制代码

我们依旧看最前面的代码,a.constructor返回的是a的构造函数,也就是Person,其实实例对象a本身并没有constructor属性,但是a中的__proto__拥有constructor属性,没错,a本身没有,就会从它的__proto__属性中寻找constructor方法,如果还没有,就继续从__proto__属性的__proto__属性中寻找...这样就构成了一个原型链。



我们似乎已经习惯了用new的方式来构造对象,其实new方式的核心实现要分为三个步骤,如下:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



Person.prototype.show=function(){

console.log(this.name);

};



Person.prototype.sex=''male'';



vara={};//1

a.__proto__=Person.prototype;//2

Person.call(a);//3



console.log(a.sex);//male

a.show();//hanzichi

复制代码

以上代码一目了然。先初始化一个空对象,然后空对象继承构造函数的prototype值,最后call构造函数初始化。



再来看一段稍微复杂一点的代码:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



Person.prototype.show=function(){

console.log(this.name);

};



Person.prototype.sex=''male'';



functionChild(){};

Child.prototype=newPerson();



vara=newChild();

console.log(a);

复制代码





这是一种简单的继承代码,先不管代码对错,我们看看代码执行中发生了什么。



Person函数前面已经分析了,我们又构造了一个Child函数,我们把一个实例化的Person对象(newPerson())赋值给了Child的prototype属性,也就是说Child继承了Person的所有方法属性,可以可以尝试打印Child.prototype看看(其值其实也就是上图中的a.__proto__),这样Child的实例就能使用Person的属性方法了。而以上实例对象a如果要调用show函数需经过两个的__proto__原型链传递:







学以致用



试着来做道题看看有没有理解:



复制代码

functiont1(name){

if(name)this.name=name;

}



functiont2(name){

this.name=name;

}



functiont3(name){

this.name=name||"test";

}



t1.prototype.name="hanzichi";

t2.prototype.name="hanzichi";

t2.prototype.name="hanzichi";



console.log(newt1().name,newt2().name,newt3().name);

复制代码

答案:hanzichiundefinedtest



其实也就是本身有name属性就用,没有就从原型链中寻找。2和3的话都是本身已经拥有,而1是本身没有name属性。ps:没有传入实参而在函数中使用形参的话会被解释成undefined。



恩,再看一道:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



Person.prototype.sex=''female'';



vara=newPerson();

console.log(a.sex);



Person.prototype.sex=''male'';

console.log(a.sex);



Person.prototype={

sex:''female''

};



console.log(a.sex);

复制代码

答案:femalemalemale



为什么会这样?







一开始Person.prototype指向一个对象,如上图1所示指向对象1,而初始化一个实例后,该实例的__proto__属性同时指向了Person.prototype,即指向了Person.prototype指向的对象1,如上图2,这时a.sex就会返回对象1中sex的值,而因为Person.prototype和a.__proto__引用同一个对象,所以都能改变该对象的值,如下代码也可以同时验证:



复制代码

functionPerson(){

this.name=''hanzichi'';

this.age=10;

}



Person.prototype.sex=''female'';



vara=newPerson();



a.__proto__.sex=''male'';



console.log(Person.prototype.sex);//male

console.log(a.sex);//male

复制代码

而Person.prototype={...}后Person.prototype引用了一个新的对象,如上图3操作后Person.prototype引用了对象2,但是实例a还是引用在原来的对象1上。



总结



javascript中每个对象除了本身的属性外,还有一个__proto__属性,继承了父对象的方法和属性(形成原型链);而每个函数有个prototype属性,该属性值是个对象,该对象函数自定义的一些属性方法外,还有两个属性,constructor(其值一般为函数本身)和__proto__(其值继承自父对象)。

献花(0)
+1
(本文系thedust79首藏)