沐光

记录在前端之路的点点滴滴

关于 JS 中“继承”的写法

前言

在巩固继承这部分知识时,突然想到早期分享时同事提出的一些疑问,即实现 JS 继承具体有那些方法。在经过一番调研后,此篇便是我所总结出的比较全面的方法。当然,文中记录的仅是一些基础的继承方法以及它们之间的的区别,以供参考。

(原型)继承

JavaScript 中有许多模拟类行为的方法,其中“继承”(更贴切的说法是委托)便是其中的一项,没有“继承”机制,JavaScript 中的类就是一个空架子。

假如我们以此例作为继承的模板,归结下来总共有 5 种继承的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父类
function Father(age) {
this.age = age;
}
Father.prototype.getAge = function () {
return this.age;
};

// 子类
function Child(age, size) {
Father.call(this, age);
this.size = size;
}

/* 继承的实现,看下例 */

// 实例
const child = new Child(28, 'small');

隐式链接法

ES6 之前比较非常规的写法是手动更改 __proto__ 属性来最简化链式继承。

1
Child.prototype.__proto__ = Father.prototype;

优点:操作很简便直接

缺点:该方法并非标准,并且无法兼容所有浏览器,慎用。

直接引用法

直接将子类的 prototype 对象指向父类的 prototype 对象。

1
Child.prototype = Father.prototype;

优点:操作简单

缺点: Child 的 prototype 属性修改会直接影响到父类,此写法其实相当于直接在父类进行修改,新创建一个子类有些多此一举。

实例化方法

子类的 prototype 属性为父类的实例对象,借由对象的 prototype 链来传递。

1
2
Child.prototype = new Father();
// Child.prototype.constructor = Chlid;

优点:能基本满足“继承”的需求

缺点:

  1. 需要创建一个新对象,然后将旧对象(原本存在的 prototype 对象,带有 constructor)抛弃掉,不能直接修改默认的 prototype 对象。
  2. 会产生副作用,比如:原型(prototype)上会有“脏”属性,本例的 prototype 上会有多余的 age 属性。
  3. 必要时还得自己重写一下 Child 的 constructor 指向,如“寄生组合式继承”。

“寄生组合式继承” 在 ES5 中为最佳写法,如果不考虑其稍微的复杂性。

对象构造法

通过 Object.create 方法来构造一个合适的关联对象。

1
2
Child.prototype = Object.create(Father.prototype);
// Child.prototype.constructor = Chlid;

优点:在 ES6 之前的最佳之选,优于实例化方法。

缺点:与实例化方法基本相同,但没有副作用。此外,其隐式 __proto__ 链比实例化方法要多一层,链式查找方法相对会耗时长一点点,基本可忽略。

设置原型法

使用 ES6 的原型设置方法,此法在 ES6 环境为最佳之选,没有之一。

1
Object.setPrototypeOf(Child.prototype, Father.prototype);

优点:能覆盖所有需求,清晰易懂,可读性强。

缺点:性能比 Object.create 稍微差点,如果考虑性能的话可优先使用 Object.create 方法

参考文档

MDN