Skip to content

js 如何实现继承

  • 借助 call
  • 借助原型链
  • 前两种结合
  • 组合继承的优化 1
  • 组合继承的优化 2(推荐)
  • ES6 的 extends 被编译后的 JavaScript 代码

<!-- more -->

借助 call

js
function Parent() {
  this.name = "parent";
}
function Child() {
  Parent.call(this);
  this.type = "child";
}
console.log(new Child());

问题:父类对象中的方法子类无法继承,只能拿到父类的属性值

借助原型链

js
function Parent() {
  this.name = "parent";
  this.play = [1, 2, 3];
}
function Child() {
  this.type = "child";
}
Child2.prototype = new Parent();

console.log(new Child());

问题:引用类型指向的是同一个内存地址

前两种结合

js
function Parent() {
  this.name = "parent";
  this.play = [1, 2, 3];
}

function Child() {
  this.type = "child";
  Parent.call(this);
}
Child.prototype = new Parent();

var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

问题:Parent 的构造函数会多执行了一次(Child.prototype = new Parent();)

组合继承的优化 1

js
function Parent() {
  this.name = "parent";
  this.play = [1, 2, 3];
}

function Child() {
  Parent.call(this);
  this.type = "child";
}

Child.prototype = Parent.prototype;

问题:将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问

但是子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4

js
var s3 = new Child();
var s4 = new Child();
console.log(s3);

20210615193516

组合继承的优化 2(推荐)

这是最推荐的一种方式,接近完美的继承,它的名字也叫做寄生组合继承

js
function Parent() {
  this.name = "parent";
  this.play = [1, 2, 3];
}
function Child() {
  Parent5.call(this);
  this.type = "child";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

ES6 的 extends 被编译后的 JavaScript 代码

es6 的 extends 经过 babel 编译之后的代码

js
function _possibleConstructorReturn(self, call) {
  // ...
  return call && (typeof call === "object" || typeof call === "function")
    ? call
    : self;
}

function _inherits(subClass, superClass) {
  // ...
  //看到没有
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true,
    },
  });
  if (superClass)
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : (subClass.__proto__ = superClass);
}

var Parent = function Parent() {
  // 验证是否是 Parent 构造出来的 this
  _classCallCheck(this, Parent);
};

var Child = (function(_Parent) {
  _inherits(Child, _Parent);

  function Child() {
    _classCallCheck(this, Child);

    return _possibleConstructorReturn(
      this,
      (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)
    );
  }

  return Child;
})(Parent);

核心是_inherits 函数,可以看到它采用的依然也是第五种方式————寄生组合继承方式,同时证明了这种方式的成功。不过这里加了一个 Object.setPrototypeOf(subClass, superClass),这是用来干啥的呢?

答案是用来继承父类的静态方法。这也是原来的继承方式疏忽掉的地方。

继承的最大问题在于:无法决定继承哪些属性,所有属性都得继承。组合,这也是当今编程语法发展的趋势,比如 golang 完全采用的是面向组合的设计方式

参考链接