上篇总结了创建对象的一些方法,这篇来总结一下继承与原型链。
例子里的代码都出自高程三。
用原型链实现继承
- 原型链的概念
让子类型的原型对象等于父类型的实例,子类型的原型对象将包含指向父类型原型对象的[[prototype]]指针,父类型的原型对象包含指向父类型构造函数的指针(constructor),假如父类型的原型对象又等于另一个原型对象的实例,那么上述关系依然成立,层层递进就构成了实例与原型的链条。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();//子类的原型对象等于超类的实例,从而获得了指向超类原型对象的[[prototype]]指针
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
所有函数的默认原型都是Object 的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。
- 原型链的问题
- 包含引用类型值的原型属性会被所有实例共享,通过原型实现继承时,超类型的实例属性变成子类型的原型属性,被所有子类型的实例共享。
- 创建子类型的实例时不能向超类型构造函数中传递参数。
借用构造函数实现继承
通过使用apply()和call()方法在子类型构造函数内部调用超类型的构造函数
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;//为了确保SuperType 构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
问题:方法都在构造函数中定义,不能复用。
组合继承
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
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);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
上面这个例子里,组合使用构造函数模式(实例属性)和原型模式(共享的属性和方法)创建了SuperType超类型对象,然后又让SubType组合使用构造函数和原型链分别实现了对实例属性的继承和对原型属性和方法的继承。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点:在子类型构造函数内部执行环境下调用超类型构造函数实现对实例属性的继承,用原型链实现对原型属性和方法的继承,既可以给构造函数传递参数,又可以实现方法的复用,又能保证每个实例都有自己的属性。
组合继承的问题:无论什么情况下都要调用两次超类型构造函数。
见下面的代码:
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); //第二次调用SuperType(),在子类型构造函数内部
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用SuperType(),创建超类型实例
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
寄生组合式继承
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
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 = new SuperType(); 减少了一次SuperType构造函数的调用
SubType.prototype.sayAge = function(){
alert(this.age);
};
上面这个例子只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()。
寄生组合式继承是引用类型最理想的继承范式。
虽然看完了这一章,但是具体的应用还需要慢慢学习,有些东西可能以后再看还会有更深入,更具体的理解,希望到时候还能来这里补充。
“对象”果然不是那么好搞定的~