原型链
ECMAScript中将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
- 基本模式
function SuperType() {
this.property = true;
this.colors = ["a", "b"];
}
SuperType.prototype.getSuperValue = function(){
console.log(this.property);
};
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType();
// SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getSubValue = function(){
console.log(this.subProperty);
};
var instance = new SubType();
instance.getSuperValue();
结果:
上面的例子中,instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然还在SuperType.prototype中,但property则位于SubType.prototype中。这是因为prototype是一个实例属性,而getSuperValue()则是一个原型方法。
注意:原型链虽然很强大,可以实现继承,但存在两个主要的问题。
(1)包含引用类型值的原型属性会被所有实例共享,这会导致对一个实例的修改会影响另一个实例。
(2)在创建子类型的实例时,不能向超类型的构造函数中传递参数。由于这两个问题的存在,实践中很少单独使用原型链。
function SuperType() {
this.property = true;
this.colors = ["a", "b"];
}
SuperType.prototype.getSuperValue = function(){
console.log(this.property);
};
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType();
// SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getSubValue = function(){
console.log(this.subProperty);
};
var instance1 = new SubType();
instance1.colors.push("c");
console.log(instance1.colors);
var instance2 = new SubType();
console.log(instance2.colors);
结果:
会发现引用类型值的原型属性会被所有实例共享。
- 借用构造函数
在解决原型中包含引用类型值所带来的问题中,使用借用构造函数技术来解决。借用构造函数的基本思想,即在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法可以在新创建的对象上执行构造函数。如下例子
function SuperType() {
this.colors = ["a", "b"];
}
function SubType() {
SuperType.call(this);
this.subProperty = false;
}
var instance1 = new SubType();
instance1.colors.push("c");
console.log(instance1.colors);
var instance2 = new SubType();
console.log(instance2.colors);
结果:
上面例子中,通过使用call()方法(或者apply()方法),在新创建的SubType实例的环境下调用了SuperType构造函数。这样一来就会在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例都会有自己的colors属性副本。
相对于原型链而言,借用构造函数可以在子类型构造函数中向超类型构造函数传递参数。如下例子
function SuperType(name) {
this.name = name;
}
function SubType() {
SuperType.call(this, "Lee");
this.age = 28;
}
var instance1 = new SubType();
console.log(instance1.name);
console.log(instance1.age);
结果:
借用构造函数存在两个问题:
(1)无法避免构造函数模式存在的问题,方法都在构造函数中定义,因此无法复用函数。
(2)在超类型的原型中定义的方法,对子类型而言是不可见的。因此这种技术很少单独使用。
- 组合继承
组合继承,指的是将原型链和借用构造函数的技术组合到一起。思路是使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。以下例子充分说明了这一点
function SuperType(name) {
this.name = name;
this.colors = ["a", "b"];
}
SuperType.prototype.sayName = function(){
console.log(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(){
console.log(this.age);
};
var instance1 = new SubType("Lee", 28);
instance1.colors.push("c");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType("Hui", 27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
结果:
这个例子中,两个实例既分别拥有自己的属性,包括colors属性,又可以使用相同的方法。
组合继承避免了原型链和借用构造函数的缺点,融合了他们的优点,是JavaScript中最常用的继承模式。
缺点:实际上子类上会拥有超类的两份属性,只是子类的属性覆盖了超类的属性
- 原型式继承
采用原型式继承并不需要定义一个类,传入参数obj,生成一个继承obj对象的对象
function objectCreate(obj){
function F(){}
F.prototype = obj
return new F()
}
直接通过对象生成一个继承该对象的对象,但是不是类式继承,而是原型式基础,缺少了类的概念。
- 寄生式继承
创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象
function objectCreate(obj){
function F(){}
F.prototype = obj
return new F()
}
function createSubObj(superInstance){
var clone = objectCreate(superInstance)
clone.property = 'Sub Property'
return clone
}
原型式继承的一种拓展,但是依旧没有类的概念。
- 寄生组合式继承
结合寄生式继承和组合式继承,完美实现不带两份超类属性的继承方式
function inheritPrototype(Super,Sub){
var superProtoClone = Object.Create(Super.prototype)
superProtoClone.constructor = Sub
Sub.prototype = Super
}
function Sub(){
Super.call()
Sub.property = 'Sub Property'
}
inheritPrototype(Super,Sub)
完美实现继承,解决了组合式继承带两份属性的问题,过于繁琐,故不如组合继承。