JavaScript 继承机制历史回顾
javascript最开始被设计为一款运行在浏览器上的简易语言,当时面向对象语言C++/JAVA大行其道。javascript之父 Brendan Eich无疑受到了影响,而且更进一步将javascript中所有的数据类型都设计为了对象(Object)。如果真的作为一种简单易用的语言,其实是不需要拥有“继承”机制的,但是既然JavaScript中都是对象,那怎么将所有的对象联系到一起呢?咳咳,最后Brendan Eich还是设计了“继承”。
但他还是不想引入“类”的概念,因为这样会将JavaScript语言变得很正式,提高了入门的难度,他的初衷是一门简单易用的脚本语言而已,最终决定使用new 操作符从原型对象生成一个实例对象。使用new后面跟着的构造函数作为原型对象,如下所示生成一个person的实例person1
function person(name){
this.name = name;//this对象代表新创建的实例对象
}
var person1 = new Person('feng')
但是,使用构造函数生成一个实例,有一个缺点那就是无法共享属性和方法,如下所示
function person(name){
<span style="white-space:pre"> </span>this.name = name;//this对象代表新创建的实例对象
<span style="white-space:pre"> </span>this.species = '哺乳类';
}
var person1 = new person('feng');//this.name = 'feng'
var person2 = new Person('bling');//this.name = 'bling'
person1.species = '爬行类';
alert(person2.species);//哺乳类,不会受person1实例属性的改变而影响
考虑到这一点,Brendan Eich决定引入一个全新的名词“prototype”作为构造器的属性,这个属性指向一个对象prototype(原型对象),这个原型对象包含着对象所有需要共享的属性和方法,而不需要共享的属性和方法则放到了构造器中。 实例对象一旦创建就自动引入原型对象的属性和方法,属性和方法沿着原型链进行查找 。还是上面那个例子修改一下,如下所示
function person(name){
<span style="white-space:pre"> </span>this.name = name;//this对象代表新创建的实例对象
}
person.prototype = {
species :'哺乳类'
}
var person1 = new person('feng');//this.name = 'feng'
var person2 = new person('bling');//this.name = 'bling'
alert(person1.species);//哺乳类
alert(person2.species);//哺乳类
这样所有的实例对象就“继承了”原型对象中的属性和方法,通过实例对象的原型。
JavaScript 继承实现原理
初入江湖:原型继承
JavaScript将原型链作为实现继承的主要方法。当代码读取某个对象的某个属性的时候,都会按照上图中存在的实现执行一遍搜索,目标是具有给定名字的属性。搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。搜索时的这个路线抽象成为了所谓的原型链。
注:写一个函数来判断定属性属于原型中还是存在于对象中
function hasPrototypeProperty(object,name){
return !object.hasOwnPrototype(name) &&( name in object);
}
name in object->只要name能被访问,就返回true,无论远行中还是对象中;object.hasOwnPrototype(name)->对象本身拥有name属性返回true。所以,当name属性存在于原型中时,函数返回true
下面演示代码的基本原理是利用原型让一个subType引用superType的属性和方法。将subType的prototype属性设为superType的实例,这时候subType的原型链中就包含了superType的属性和方法,大致是这样的
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subProperty=false;
}
SubType.prototype=new SuperType();//让subType的原型指向SuperType的原型
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
这样SubType就实现了对SuperType的继承,看个图
(原型中的construnctor属性指回那个构造函数)
验证一下,是不是这个样子
var instance=new SubType();
console.log(instance.getSuperValue()); //true
console.log(instance.getSubValue()); //false
console.log(instance instanceof SuperType); //true
console.log(instance instanceof SubType); //true
果不其然,SubType的实例同时也是SuperType的实例,可以访问到SuperType的属性和方法,实现了SubType对SuperType的继承。在上面的代码中SubType没有使用其默认的prototype对象,而是使用了SuperType的实例,这样SubType的实例拥有
1.SuperType实例的属性和方法
2.SuperType的prototype的属性和方法(如上图,在原型链中SuperType的实例有指向SuperType的prototype的指针)
3.SubType实例属性和方法(subProperty)
4.SubType新的prototype对象追加的属性和方法(getSubValue)
熟悉JavaScript的同学肯定清楚:和很多强类型面向对象语言一样,JavaScript的所有引用类型都默认继承自Object对象,这个继承也是通过原型链实现的。
所有函数都是Object对象的实例,这样是不是明白了为什么所有自定义类型都会用于toString, valueOf 等默认方法了吧,看起来很不错,但是这样的继承方式有两个需要注意的地方
1.给子类型的prototype对象内追加方法和属性必须在继承了父类型实例之后,否则追加的方法、属性会被重写,也就是说上面的例子
function SubType(){
this.subProperty=false;
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
不能写成这样
function SubType(){
this.subProperty=false;
}
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
SubType.prototype=new SuperType();
2.子类型的prototype不能通过字面量赋值,这样也会重写原型链而不是追加,也就是说不能写成这样
function SubType(){
this.subProperty=false;
}
SubType.prototype=new SuperType();
SubType.prototype={
getSubValue:function(){
return this.subProperty;
}
}
名动一方:组合继承
上面的经典继承方式是不完美的,当父类型中存在引用类型的属性的时候,子类型的所有实例会共享这些属性,一处修改,处处更新,这样是不安全的
function SuperType(){
this.property=true;
this.colors=['red','blue','green'];
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subProperty=false;
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
var sub1=new SubType();
sub1.colors.push('yellow');
var sub2=new SubType();
console.log(sub2.colors);//["red", "blue", "green", "yellow"]
确实是这样的,但是这个错误很熟悉的赶脚有没有,在javascript创建对象的过程中也会有这个问题。解决方法:子类型构造函数中引入父类型的构造函数,使实例拥有自己独有的属性
function SuperType(){
this.property=true;
this.colors=['red','blue','green'];
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subProperty=false;
<span style="color:#ff0000;"> SuperType.call(this);</span>
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
var sub1=new SubType();
sub1.colors.push('yellow');
var sub2=new SubType();
console.log(sub2.colors);//["red", "blue", "green"]
这样在子类的构造函数中调用父类的构造函数,这样的写法实际上是在新创建的SubType的实例环境下调用SuperType的构造函数,实例中执行SuperType函数中对象初始化代码,Subtype的每个实例就会有自己的colors副本了。这样的继承方式在坊间被称为组合式继承。(额,你是否联想到了java中的super()...,一样的道理)
这样做还多了个好处,可以传递参数了
function SuperType(name){
this.property=name;
this.colors=['red','blue','green'];
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(name){
this.subProperty=false;
SuperType.call(this,name);
}
SubType.prototype=new SuperType();
//或者将这行代码替换为
//SubType.prototype = Object.create(SuperType.prototype);//Object.create创建一个空对象,并且对象的原型指向参数SuperType.prototype
//SubType.prototype.constructor = SubType//若不设置的话,SubType.prototype会指向SuperType
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
var sub1=new SubType('Byron');
console.log(sub1.property);//Byron
一代宗师:寄生组合式继承
看起来使用组合式继承很完美了,但是...总是有但是,使用组合继承总会调用两次父类的构造函数,这样父类的实例属性和方法在子类的实例中会有两份。父类初始化两次,这有时会导致一些问题,举个例子,父类构造函数中有个alert,那么创建子类实例时,会发现有两次弹框。
function SubType(name){
this.subProperty=false;
SuperType.call(this,name); //第一次
}
SubType.prototype=new SuperType(); //第二次
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
可以看到子类的原型中也包含了父类的属性,只不过因为解释器的属性查找机制,被子类的属性所覆盖,只要子类的特权成员被删除,原型中相应的成员就会暴露出来:
问题的根源就是调用两次SuperType的构造函数,其实在第一次调用的时候SubType就已经获得了SuperType的实例属性和方法,第二次调用的时候仅需要SuperType的prototype属性就可以了,因此可以这样写
function extend(subType,superType){
var _prototype=new Object(superType.prototype); //得到父类prototype对象副本
_prototype.constructor=subType; //constructor属性改为子类自己的
subType.prototype=_prototype; //重写子类prototype
}
function SuperType(name){
this.property=name;
this.colors=['red','blue','green'];
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(name){
this.subProperty=false;
SuperType.call(this,name);
}
extend(SubType,SuperType);
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
未完待续。。。。。。