导读
JavaScript中主要有三种实现继承的方式,分别是
- 构造函数继承
- 原型继承
- 组合继承
其中前两种方式都有其缺陷。第三种方式组合继承则将前两种方式结合起来,取长补短,是JS继承最常用的最佳实践。本文结合代码和注释逐一阐述三种继承方式。
构造函数继承
构造函数继承的关键: 在Child构造函数中执行Parent.call(this)
。
function Parent(name) {
this.name = name;
this.hobby = [];
this.speak = function() {
console.log("Parent speak");
} // 缺点1:new多个Child时,Parent构造函数中的方法会在每个Child中拷贝一份,浪费内存
}
Parent.prototype.say = function() {
console.log("Parent say");
} // 缺点2:Parent原型对象上的方法不会被Child继承
function Child(name, type) {
Parent.call(this, name); // 构造函数继承的关键
this.type = type;
}
原型继承
原型继承的关键: 设置Child原型指向ParentChild.prototype = new Parent()
。
function Parent(name) {
this.name = name;
this.hobby = []; // 缺点:Parent的引用属性会被所有Child实例共享,互相干扰
}
Parent.prototype.say = function() {
console.log("Parent say");
}
function Child(type) {
this.type = type;
}
Child.prototype = new Parent(); // 原型继承的关键
组合继承(最佳实践)
组合继承的关键:
- 属性使用构造函数继承 —— 避免了原型继承中Parent引用属性被所有Child实例共享的缺陷。
- 方法使用原型继承 —— 避免了构造函数继承中方法重复拷贝、浪费内存的缺陷。
function Parent(name) {
this.name = name;
this.hobby = []; // 属性放在构造函数中
}
Parent.prototype.say = function() { // 方法放在原型中
console.log("Parent say");
}
function Child(name, type) {
Parent.call(this, name); // Child继承Parent属性(构造函数继承)
this.type = type; // Child扩展属性
}
Child.prototype = Object.create(Parent.prototype); // Child继承Parent方法(原型继承)
Child.prototype.speak = function() { // Child扩展方法
console.log("Child speak");
}
Child.prototype.constructor = Child; // 修复Child的constructor指向,否则Child的constructor会指向Parent
补充:
对于组合继承代码中的Child.prototype = Object.create(Parent.prototype)
,还有两种常见的类似写法是Child.prototype = Parent.prototype
和Child.prototype = new Parent()
,但这两种写法都是有缺陷的,需要避免:
-
Child.prototype = Parent.prototype
,修改Child.prototype就等于修改Parent.prototype,会干扰所有Parent实例。 -
Child.prototype = new Parent()
,Parent构造函数重复调用了两次(另一处调用是Child构造函数中的Parent.call(this)
),浪费效率,且如果Parent构造函数有副作用,重复调用可能造成不良后果。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。