原型
原型的基本知识
什么是原型?
在js中,我们创建的每个函数都有一个prototype属性,该属性是一个指针,指向一个对象。而这个prototype属性就是原型(也可以叫原型对象,因为其本身是一个对象)
原型的默认属性
首先明白下列知识点:
js中的对象都会有一个内置的属性——[[Prototype]](在Chrome、Firefox等浏览器中,其被形象化为__proto__)。该属性同样是一个指针,指向其他对象(其究竟指向哪个对象,后面会说到)
所有原型对象都会获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针
例如:
function Person(){
}
则:
Person.prototype.constructor指向Person,因为prototype ∈ Person
通过上述两个知识的铺垫,我们便可知:
在原型中一定有:constructor、__proto__两个属性
原型与构造函数实例的关系
我们前面说过——‘js中的对象都会有一个内置的属性——[[Prototype]](__proto__)’,因此,默认的,实例中也会内置有__proto__属性(注意哈:实例中没有constructor属性哈)。
而当我们调用构造函数创建一个实例后,实例内置的__proto__属性会指向原型
下面用一段代码及一张图来展现这些关系:
function Person(){
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
原型链与继承
在叙述原型链之前,先明白:
- 我们可以重写默认的原型对象,让prototype指向一个我们自己创建的对象。如:
function Person(){
}
Person.prototype = {
name : 'Nicholas',
age : 29,
job : 'Software Engineer',
sayName : function(){
alert(this.name);
}
}
在未重写Person的原型对象前(默认的原型对象),原型对象为:
{
constructor : Person,
__proto__ : Object.prototype
}
重写后的原型对象为:
{
name : 'Nicholas',
age : 29,
job : 'Software Engineer',
sayName : function(){
alert(this.name);
}
__proto__ : Object.prototype/*前面说过,任何对象都有__proto__*/
}
可见,constructor便不会直接存在于原型对象上了
我们依旧可以自己设置constructor,但是相对于默认的constructor是不可枚举的而言,我们自己设置的constructor是可枚举的
下面我们再来说原型链:
我们利用上述自己重写原型对象的理论,让一个构造函数的prototype指向另一个构造函数的实例,这样以此类推,那么就形成了一个原型链
而JS中的继承就是依靠原型链来实现的。(原型链与继承是分不开的,一旦形成了原型链,那么就存在继承了)
下面我们用一个图来说明原型链:
上图中,B继承了A,C又继承了B,C也间接继承了A。
而且还发现,通过原型链,我们虽然建立了继承,但是,很多我们不想继承的东西也会继承过来。比如:
B继承A时,我们也将A实例中的属性或方法继承了过来。关于这个问题的解决后面会提到
所有普通的原型链最终都会指向内置的Object.prototype
读写对象属性时的搜索路径
介绍完原型与原型链后,我们再来讨论,当我们读写一个对象的属性时,内部是怎么去寻找这个属性的……
在不考虑其他情况下,读写属性时的搜索路径是比较简单的。如下:
- 当你读取对象的属性时会触发[[Get]]操作。而对于默认的[[Get]]操作:第一步是检查对象本身是否有这个属性,如果有的话就返回它的值。如果对象本身不存在该属性,就会访问原型链。如果原型链上也未找到该属性,那么就返回undefined。
- 当你给属性赋值时会触发[[Put]]操作,而对于默认的[[Put]]操作:
1.如果对象本身有该属性,则修改本身的这个属性。而无论原型链上是否存在该属性(这叫做屏蔽——即对象本身的属性会屏蔽掉原型链上的同名属性)。
2.如果对象本身及其原型链上都没有该属性或者对象本身没有但是其原型链上有,那么该属性会被直接添加于对象本身
其实总结一下就是:我们不能通过对象实例为该对象的原型(或原型链)设置属性,虽然我们能读取原型(或原型链)上的属性
注意:上述只在我们平时常用的情况下成立,还有一些特殊情况是不成立的——具体可以翻阅《你不知道的JavaScript》上卷