经典继承(借用构造函数)
为了解决之前提到的超类型构造函数中引用类型的问题,我们可以使用借用构造函数的方式
function SuperType() {
this.property = ['red'];
}
function SubType() {
SuperType.call(this); // 借用构造函数
}
var instance = new SubType();
alert(instance instanceof SubType);
alert(instance instanceof SuperType);
我们分析一下这种方式:
在SubType中通过call,让SuperType的构造函数在自己的运行环境中运行,SuperType中的this就指向了SubType的作用域,等于给SubType添加了property的对象,故实现了每个实例有自己独立的超类型引用对象。
但是这种方法缺点太过于明显:
- 这样写,这两个对象没有继承关系了,像代码中使用的instanceof运算符,不能确定实例和超类的关系了。
- 之前我们在创建对象的时候讲过,将对象属性的定义写道构造函数中,将方法的定义写到原型中可以避免方法的重复创建,这种模式无法识别不写在构造函数中的代码。
组合继承(推荐)
其实上面的方式我们已经很接近成功了,为了解决缺点1,我们把SubType的原型指向SuperType的一个实例看看会发生什么情况。
function SuperType() {
this.property = ['red'];
}
function SubType() {
SuperType.call(this);
}
SubType.prototype = new SuperType();
var instance = new SubType();
alert(instance instanceof SubType);
alert(instance instanceof SuperType); //true
var instance2 = new SubType();
instance.property.push('blue');
alert(instance2.property); //red
我们发现,当我们这么做的时候,既维持了继承关系,也为每一个实例创建了自己的property属性。
这种方式是我们在实际应用中最常使用的方式。
原型式继承
当我们不需要创建一类型实例,而是只是想简单的创建一个对象的副本的时候,我们既可以用原型式继承,其作用就像是创建了对象的一个克隆,但是这个克隆是浅复制的。
其原理很简单,就是下面的方法
// 本质是返回一个以传入对象为原型对象的实例
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
这种方法可以轻量级的创建一个之前对象的副本,注意其是浅复制的,无法避免引用类型的问题。
ES5对于这种创建方式进行了规范化,使用下面的方法 Object.create:
var person = {
name: 'nic',
friend: ['red']
};
var another = Object.create(person);
var another2 = Object.create(person, {
name: {
// 注意这里的属性描述必须是一个对象
value: 'Greg'
}
});
alert(another2.name);
注意第二个参数是需要复写的属性的属性描述符,不能直接写 属性:值。
寄生组合继承(标准继承)
当我们使用组合继承的时候,需要把子类型的prototype属性赋值为父类型的一个实例,这就导致父类型的构造函数被调用了两次,一次是在SuperType.call(this);的时候,一次是在SubType.prototype = new SuperType();的时候。构造函数多次的执行会导致性能的消耗,所以为了避免这种情况,结合我们之前讲的对象复制的方法,我们使用下面这种寄生组合模式。
function inheritPrototype(subType, superType) {
// 使用复制的方法,创建出的对象不会打破原型链
// 如果不理解这里,返回去看object.create的实现
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType() {
this.property = ['red'];
}
function SubType() {
SuperType.call(this); //引用类型都复制出了副本
}
inheritPrototype(SubType, SuperType);
var instance = new SubType();
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
我们注意下面几点:
- 为什么说用Object.create节省了性能呢?因为Object.create的实现是:
其中,F构造函数中没有代码,节省性能,而调用SuperType的构造方法,里面代码很多,初始化了很多属性,但是在SuperType.call(this);的时候,这些属性又被重新初始化出了一个副本,所以这部分性能就等于浪费掉了function object(o) { // 这里等于返回了一个o的构造函数中没有代码的子类 // 在寄生组合模式中,返回的这个子类正好用于做SubType的prototype function F() {} F.prototype = o; return new F(); }
- 由于前面讲的Object.create的实现,所以不影响原型链
- 注意inheritPrototype中constructor的引用指向要处理对