JavaScript实现继承的方式主要有两种: 原型链继承和借助构造函数继承
一、原型链继承
原型链继承的主要思想是将父类型的实例赋给子类型的原型,这样子类型就可以通过原型对象的[[prototype]]访问到父类型的所有属性和方法。具体实现方式如下:
function SuperType()
{
this.property = true; //SuperType 实例属性 property
}
SuperType.prototype.getSuperValue = function()
{
return this.property; //SuperType 方法 getSuperValue()
}
function SubType()
{
this.subProperty = false; //SubType 实例属性 subProperty
};
SubType.prototype = new SuperType(); //将SuperType实例赋给SubType的原型
SubType.prototype.constructor = SubType;
SubType.prototype.getSubValue = function()
{
return this.subProperty; //SubType 添加方法 getSubValue()
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
父类型SuperType的实例中有一个内部指针[[prototype]]指向SuperType的原型,而将这个实例赋值给子类型SubType的原型后,instance在搜索属性时,会先搜索自身的属性,然后搜索它的原型SubType.prototype中的属性,包括SuperType的实例属性和后添加自身的原型属性,然后会继续搜索到SuperType.prototype的属性。这样SubType就可以访问到SuperType的全部属性了。也就是实现了继承。这个继承是通过在它们之间建立原型链实现的。
不过原型链继承有一些问题。首先,子类型不能像父类型传递参数,其次,由于父类型的实例属性都变成了子类型的原型属性,那么那些引用类型的属性值就会在子类型的所有实例中共享,这样往往跟我们预期的效果不同。
二、借助构造函数继承
相对于原型中引用类型共用的问题,构造函数就有自己的突出优势,这在之前讲解创建对象方法时也提到过。借助构造函数继承的思想是在子类构造函数中调用父类构造函数。具体实现方法如下:
function SuperType(name)
{
this.name = name;
this.friends = ["Mike", "John"];
}
function SubType()
{
// 调用SuperType构造函数,并为其传递一个参数,设置了SubType的name和friends属性值
SuperType.call(this, "Nicholas");
//为SubType添加属性age
this.age = "22";
}
var instance1 = new SubType();
alert(instance1.name); // Nicholas
instance1.friends.push("Kate");
alert(instance1.friends); // Mike,John,Kate
alert(instance1.age); // 22
var instance2 = new SuperType("Jake");
alert(instance2.friends); // Mike,John
alert(instance2.name); // Jake
在SubType的构造函数中通过call()方法调用SuperType的构造函数,这样所有SubType的实例都会有一个firends副本,不存在共用的问题。并且可以在调用SuperType构造函数时传入参数。
当然这种方法的缺点也是显而易见的,它无法实现函数的复用。
三、混入式继承
function Dog (){
this.name = "旺财";
this.age = 0.5;
}
var obj = {
run : function(){
console.log("跑....");
},
eat : function(){
console.log("吃饱了才有里气跑");
}
}
//混入式继承
// for(var key in obj){
// Dog.prototype[key] = obj[key];
// }
var d1 = new Dog();
d1.eat(); // 吃饱了才有里气跑
四、原型式继承
基本想法:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
function Dog (){
this.name = "旺财";
this.age = 0.5;
}
var obj = {
run : function(){
console.log("跑....");
},
eat : function(){
console.log("吃饱了才有里气跑");
}
}
//直接原型替换
Dog.prototype = obj;
var d1 = new Dog();
var d2 = new Dog();
// d1.eat(); // 吃饱了才有力气跑...
// d2.eat(); // 吃饱了才有力气跑...
五、寄生组合式继承
我们首先要知道这两次调用父类的构造函数的真正目的是什么。第一次调用,将父类的实例赋值给子类的实例,我们本质上需要的只有指向父类原型的指针[[prototype]]而已,它使父类和子类通过原型链连接起来,而父类的实例属性我们并不需要,它的继承是通过第二次在子类的构造函数中调用实现的。所以,我们现在只要能想出一个方法,能够获得指向父类原型的指针,并把它添加到子类的原型上,就可以省掉第一次调用了。
道格拉斯•克罗克福德在2006年提出了原型式继承方法,在其中用到了这样一个函数:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
在这个函数中,首先创建了一个临时的空的构造函数F,然后将参数o赋给F的原型,最后返回一个F的实例。如果把这个函数用在继承的实现中,并将父类的原型对象作为参数传入,就正好满足我们之前的要求:F的原型中保存了父类原型对象的副本,可以将F赋给子类的原型,那么子类就可以访问到父类所有的原型属性了。这个过程可以用下面这个函数包装起来:
function inheritPrototype(subType, superType) {
var proto = object(superType.prototype);
proto.constructor = subType;
subType.prototype = proto;
}
在函数内部,先创建一个父类的原型副本proto, 然后为其添加constructor属性,最后将这个副本赋给子类。这样就可以不通过调用父类的构造函数建立原型链。用这个函数代替之前组合继承方式中的为子类原型赋值的语句就可以了。
function SuperType(name)
{
this.name = name;
this.friends = ["Mike","John"];
}
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);
};
这种寄生组合式继承是目前性能最好的继承方式。
六:经典继承
// 命名空间
var itcast={
create:function (obj){
//能力检测
if(Object.create){
//es5中 Object.create(); 是一个静态方法
// var obj=Object.create(原型对象);
// es5中的新语法 ,有兼容新问题
return Object.create(obj);
}else{
function F(){};
F.prototype=obj;
return new F();
}
}
}
var data={
name:'赵四',
age:18
}
var d1=itcast.create(data);
console.log(d1.name) // 赵四