JavaScript中主要有三种实现继承的方式,分别是
- 构造函数继承
- 原型继承
- 组合继承
其中前两种方式都有其缺陷。第三种方式组合继承则将前两种方式结合起来,取长补短,是JS继承最常用的最佳实践。本文结合代码和注释逐一阐述三种继承方式。
1.构造函数继承
构造函数继承的关键: 在Child构造函数中执行Parent.call(this)。
缺点:
-
new多个Child时,Parent构造函数中的方法会在每个Child中拷贝一份,浪费内存
-
Parent原型对象上的方法不会被Child继承
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; }
2.原型继承
原型继承的关键: 设置Child原型指向Parent,即Child.prototype = new Parent()。
缺点:
-
Parent的引用属性会被所有Child实例共享,互相干扰
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(); // 原型继承的关键
3.组合继承(最佳实践)
组合继承的关键:
-
属性使用构造函数继承 —— 避免了原型继承中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构造函数有副作用,重复调用可能造成不良后果。