JavaScript实现继承的三种方式

114 阅读2分钟

导读

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.prototypeChild.prototype = new Parent(),但这两种写法都是有缺陷的,需要避免:

  • Child.prototype = Parent.prototype,修改Child.prototype就等于修改Parent.prototype,会干扰所有Parent实例。
  • Child.prototype = new Parent(),Parent构造函数重复调用了两次(另一处调用是Child构造函数中的Parent.call(this)),浪费效率,且如果Parent构造函数有副作用,重复调用可能造成不良后果。