前言:这次对上篇收个尾,主要总结一下javascript的继承。
1.原型链
js中原型链是实现继承的主要方法。基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。我们来简单回顾一下以前的内容:
每个构造函数都有一个原型对象
每个原型对象都包含一个指向构造函数的指针:(constructor)
而实例和构造函数都有一个prototype属性指针指向原型对象。
假如现在我们
让原型对象(A)等于另一个类型的实例(b)
,此时相当于这个原型对象(A)整体作为一个实例指向另一个实例的原型对象(b的原型对象B)
。以上就实现了继承。
看下面代码实例:
function SuperType(){
this.property = true; //property是SuperType的实例属性
};
SuperType.prototype.getSuperValue = function(){ //getSuperValue是SuperType的原型方法
return this.property;
};
function SubType(){
this.subproperty = false;
}
//让SuperType继承SubType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
在上面的代码中,定义了两个类型:SuperType和SubType。每个类型分别有一个属性和方法。
通过创建SuperType的实例,并赋值给了SubType.prototype,从而实现SubType继承了这个的实例,
原来存在于SuperType的实例中的所有的属性和方法,现在也存在于SubType.prototype中了。
既然现在SubType的原型对象SubType.prototype是SuperType的实例化对象,那么
SuperType的实例属性property就位于SubType.prototype
。如下图:现在instance.constructor现在指向的是SuperType,图中可以看出来。也可以在进行继承之后,再进行如下步骤:
SubType.prototype.constructor = Subtype;
2.完整原型链
所有函数的默认原型都是Object的实例
,所以下图是上面例子的完整原型链。
3.重写或添加方法到超类型
(1)重写和添加方法必须在用超类型的实例(new SuperType()
)替换原型(SubType.prototype
)之后。
function SuperType(){
this.property = true;
};
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//让SuperType继承SubType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function(){
return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false
alert((new SuperType()).getSuperValue()); //我仿照java这么写,居然返回true
重写超类型中的方法之后,通过SuperType的实例调用
getSuperValue()
时,调用的就是这个重新定义的方法。通过SuperType的实例调用
getSuperValue()
时,调用的就是超类型中的方法,返回true
(2)通过原型链实现继承时,不能使用对象字面量创建原型方法
,这样会重写原型链
function SuperType(){
this.property = true;
};
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//让SuperType继承SubType
SubType.prototype = new SuperType();
SubType.prototype = {
getSubValue: function(){
return this.subproperty;
},
someOtherMethod: function(){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error
(3)原型链的问题
在通过原型链进行继承时,原型实际上会变成另一个类型的实例,所以原先的实例属性也就变成了现在的原型属性了。
现在假如原型实例的属性是引用类型的,那么它会直接被添加成现在的对象原型的属性,那么通过这个创建的实例对这个引用类型的属性进行更改时,会立即反映在所有的实例对象上。
看下面代码:
function SuperType(){
this.colors = ["red","blue","green"];
};
function SubType(){
}
//让SubType继承SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //["red","blue","green","black"]
var instance2 = new SubType();
alert(instance2.colors); //["red","blue","green","black"]
当SubType通过原型链继承了SuperType之后,SubType.prototype就变成了SuperType的一个实例
此时
SubType拥有一个自己的colors属性
,就像专门创建了一个SubType.prototype.colors属性一样
此时SubType所有的实例话对象都会共享这个colors属性,修改instances1的colors属性会立即在instances2中显示出来。
原型链还有一个问题:在创建子类型的实例时,不能向超类型的构造函数传递参数,实际上是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
4.实现继承的其它方法
(1)借用构造函数
基本思想:
在子类型构造函数的内部调用超类型的构造函数,通过使用
call()
方法或者apply()
方法。
例子:
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
function SubType(name,age){
//继承了SuperType,同时还传递了参数
SuperType.call(this,name);
//再为子类型定义属性
this.age = age;
}
var instance1 = new SubType("Jack");
alert(instance1.name);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
上述代码中解决了一个问题,就是引用类型的属性问题,每个实例化的子类型都有自己的特有的属性
还存在一个问题,如果方法都定义在构造函数中,那么方法的就不能复用。
(2)组合继承-最常用的继承模式
组合继承的思路是:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
例子:
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//继承SuperType的属性
SuperType.call(this,name);
this.age = age;
}
//继承SuperType的方法
SubType.prototype = new SuperType();
//定义子类型自己的方法
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Jack",26);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //Jack
instance1.sayAge(); //26
var instance2 = new SubType("Rose",23);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //Rose
instance2.sayAge(); //23
(3)原型式继承
思路:借助原型可以基于已有的对象创建新对象,还不必因此创建自己的自定义类型
如下:
function object(o){
function F(){};
F.prototype = o;
return new F();
}
在
object()
函数内部先创建一个临时性的函数。然后将传入的对象作为这个构造函数的原型。
最后返回这个临时类型的饿新实例。
如下:
var person = {
name:"Jack",
friends:["路人甲","路人乙"]
};
var anotherPerson = object(person); //此处调用上方的object方法
anotherPerson.name = "Rose";
anotherPerson.friends.push("路人丙");
var yetPerson = object(person);
yetPerson.name = "Rick";
yetPerson.friends.push("路人丁");
alert(person.friends); //["路人甲","路人乙","路人丙","路人丁"]
上述person.friends不仅属于person所有,而且会被anotherPerson和yetPerson共享。
还有Object.create()
方法,前面已经总结过了。
(4)寄生式继承
思路:创建一个仅用于封装继承过程的函数。
function createAnother(original){
var clone = object(original); //调用前面的object()方法
clone.sayHi = function(){
alert("hi");
};
return clone;
}
//使用
var person = {
name:"Jack",
friends:["路人甲","路人乙","路人丙"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"Hi"
5.寄生组合式继承
(1)组合继承存在的问题
组合继承是js最常用的继承模式,不过它有自己的不足,组合继承最大的问题在于要调用两次超类型的构造函数
,一次是创建超类型的实例赋值给子类型的原型对象时
,一次是子类型构造函数内部
。
最终子类型会包含超类型对象的全部实例属性,但是我们不得不在调用子类型构造函数时重写这些属性。
看下面例子:
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name); //第二次调用
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors,它们都是SuperType的实例属性,只不过位于SubType的原型中。
当调用SubType的构造函数时,在函数内部又会调用SuperType的构造函数,又在新对象上创建了实例属性name和colors,于是这两个属性就屏蔽了原型中的同名属性。
(2)解决方法
寄生组合式继承的思想是:不必为了子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型的一个副本而已,本质上就是使用寄生式继承来继承超类型的原型,把返回的结果赋值给子类型的原型。
大家一定还记得上面说的原型式继承吧吧,将一个对象浅赋值给另一个对象,现在也可以把一个超类型的原型赋值给另一个子类型原型
1.回忆一下object()
函数的代码
function object(o){
function F(){}
F.prototype = 0;
return new F();
}
2.创建一个函数,它接收两个参数:子类型构造函数和超类型构造函数。
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
上面的代码第一步创建超类型原型的一个副本
为创建的副本添加constructor属性,弥补因重写原型而失去默认的constructor属性
此处的重写发生在object()
函数里面,超类型的原型superType.prototype直接赋给了F.prototype,然后object()
函数又返回了F的新实例。把创建新的对象赋值给子类型的原型
3.那么现在来使用一下
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(subType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
上述代码高效率,因为它只调用了一次SuperType的构造函数,因此避免了在SubType.prototype上面创建不必要的、多余的属性,
此时原型链还能保持不变。
以上~~~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。