继承的目的简单讲就是要使用父类的实例属性或原型属性
// 父类
function Animal(name, color) {
// 实例属性
this.name = name;
this.color = color;
}
// 原型属性
Animal.prototype.getName = function() {
return this.name;
}
// 子类
function Cat() {
xxxxxx
}
构造函数继承
原理:在子类的构造函数中调用父类的构造函数
function Cat(name, color) {
// 调用了父类的构造函数,将其中的this指向了子类的实例
Animal.call(this, name, color);
this.xxx = xxx;
}
优点:可以向父类构造函数传参
缺点:只能继承父类的实例属性
原型链继承 之 间接原型继承
原理:将子类原型指向父类的实例
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat;
优点:
- 子类原型的修改不会影响到父类原型
- 既能继承实例属性,也能继承原型属性
缺点:
- 不方便给父类的构造函数传参,传也只是一次性的,只能在刚开始修改子类原型的时候传
- 子类的原型是父类的实例,继承自父类实例上的属性对子类实例来说是公有的
注意:给子类添加原型方法一定要在
Cat.prototype = new Animal()
之后
原型链继承 之 直接原型继承
原理:将子类的原型指向父类的原型
// 这种做法比较激进,弊端很大
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
优点:貌似没有什么优点,不推荐使用
缺点:
- 子类没有自己的原型,就没有自己的公共方法
- 修改子类的原型会影响到父类原型,即以后不能再给子类添加原型属性
- 只能继承父类的原型属性
原型继承 之 使用空对象作为媒介
原理:emmmm,看代码
function extend(Parent) {
function F(){}
F.prototype = Parent.prototype;
return new F();
}
Cat.prototype = extend(Animal);
// 或者 Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
优点:
- 和直接原型继承相比,修改子类的原型不会影响到父类
- 和间接原型继承相比,子类的原型是一个空对象,内存占用较少(当然后期也可以加原型属性)
缺点:只能继承原型属性
拷贝继承
原理:通过 for-in 将父类上的原型属性复制到子类原型
function extend(Child, Parent) {
var c = Child.prototype;
var p = Child.prototype;
for(var key in p) {
if(!p.hasProperty(key)) return;
c[key] = p[key];
}
}
优点:emmmm...子类在继承前或继承后都可以设置原型属性
缺点:只能继承父类的原型属性,性能较差?
实际上也可以复制父类的实例,从而继承父类的实例属性,不过缺点和间接原型继承一样,父类实例的实例属性被子类实例共享。
组合继承
原理:构造函数继承 + 间接原型继承
优点:既能继承实例属性,又能继承原型属性
缺点:
- 间接原型继承的缺点:父类实例的实例属性被子类实例共享
- 调用了两次父类的构造函数,生成了两个父类实例,开销大 ----- 这里我不这么认为,我觉得使用call来调用父类构造函数并不是new调用,而是把父类的构造函数当成普通函数执行,仅仅是把this指向了子类的实例,执行父类构造函数仅仅是给子类实例添加私有属性,并不会创建父类实例。
寄生组合式继承(推荐)
原理:构造函数继承 + 空对象媒介
优点:和组合继承相比,这样就不会生成父类实例了,且子类原型是个空对象,开销较小。
总结
只继承实例属性
- 构造函数继承(能给父类构造函数灵活传参)
只继承原型属性
- 直接原型继承(不推荐)
- 空对象媒介(开销小)
- 拷贝继承(也可拷贝父类实例)
既继承实例属性,又继承原型属性
- 间接原型继承
- 组合继承(构造函数继承 + 间接原型继承)
- 寄生组合继承(构造函数继承 + 空对象媒介)