ES6 与 ES5 继承的区别

13,538 阅读5分钟

ES6 中有类 class 的概念,类 class 的继承是通过 extends 来实现的,ES5 中是通过设置构造函数的 prototype 属性,来实现继承的,ES6 与 ES5 的继承有什么区别呢?下面通过 3 个 demo,来分析它们之间的区别。

1. ES5 继承

直接上代码:

function A() {
  this.a = 'hello';
}

function B() {
  A.call(this);
  this.b = 'world';
}

B.prototype = Object.create(A.prototype, {
  constructor: { value: B, writable: true, configurable: true }
});

let b = new B();

代码中,构造函数 B 继承构造函数 A,首先让构造函数 B 的 prototype 对象中的 __proto__ 属性指向构造函数 A 的 prototype 对象,并且将构造函数 B 的 prototype 对象的 constructor 属性赋值为构造函数 B,让构造函数 B 的实例继承构造函数 A 的原型对象上的属性,然后在构造函数 B 内部的首行写上 A.call(this),让构造函数 B 的实例继承构造函数 A 的实例属性。在 ES5 中实现两个构造函数之间的继承,只需要做这两步即可。下面六幅图分别是,实例 b 的原型链及验证图,构造函数 B 的原型链及验证图,构造函数 A 的原型链及验证图。

实例 b 的原型链如下图:

b 的原型链

实例 b 的原型链验证图:

b 的原型链验证图

构造函数 B 的原型链图下图:

B 的原型链

构造函数 B 的原型链验证图图:

B 的原型链验证图

构造函数 A 的原型链图下图:

A 的原型链

构造函数 B 的原型链验证图图:

B 的原型链验证图

从上面 6 幅图可知,构造函数 B 的实例 b 继承了构造函数 A 的实例属性,继承了构造函数 A 的原型对象上的属性,继承了构造函数 Object 的原型对象上的属性。构造函数 B 是构造函数 Function 的实例,继承了构造函数 Function 的原型对象上的属性,继承了构造函数 Object 的原型对象上的属性。 构造函数 A 是构造函数 Function 的实例,继承了构造函数 Function 的原型对象上的属性,继承了构造函数 Object 的原型对象上的属性。可看出,构造函数 A 与 构造函数 B 并没有继承关系,即构造函数 B 没有继承构造函数 A 上面的属性,在 ES6 中,用 extends 实现两个类的继承,两个类之间是有继承关系的,即子类继承了父类的方法,这是 ES6 与 ES5 继承的第一点区别,下面通过 ES6 的继承来说明这一点。

2. ES6 继承

直接上代码:

class A {
  constructor() {
    this.a = 'hello';
  }
}

class B extends A {
  constructor() {
	super();
	this.b = 'world';
  }
}

let b = new B();

代码中,类 B 通过 extends 关键字继承类 A 的属性及其原型对象上的属性,通过在类 B 的 constructor 函数中执行 super() 函数,让类 B 的实例继承类 A 的实例属性,super() 的作用类似构造函数 B 中的 A.call(this),但它们是有区别的,这是 ES6 与 ES5 继承的第二点区别,这个区别会在文章的最后说明。在 ES6 中,两个类之间的继承就是通过 extends 和 super 两个关键字实现的。下面四幅图分别是,实例 b 的原型链及验证图,类 B 的原型链及验证图。

实例 b 的原型链如下图:

b 的原型链

实例 b 的原型链验证图:

b 的原型链验证图

类 B 的原型链图下图:

B 的原型链

类 B 的原型链验证图图:

B 的原型链验证图

通过上面 4 幅图可知,在 ES6 与 ES5 中,类 B 的实例 b 的原型链与构造函数 B 的实例 b 的原型链是相同的,但是在 ES6 中类 B 继承了类 A 的属性,在 ES5 中,构造函数 B 没有继承构造函数 A 的属性,这是 ES6 与 ES5 继承的第一个区别。

3. super() 与 A.call(this) 的区别

在 ES5 中,构造函数 B 的实例继承构造函数 A 的实例属性是通过 A.call(this) 来实现的,在 ES6 中,类 B 的实例继承类 A 的实例属性,是通过 super() 实现的。在不是继承原生构造函数的情况下,A.call(this) 与 super() 在功能上是没有区别的,用 babel 在线转换 将类的继承转换成 ES5 语法,babel 也是通过 A.call(this) 来模拟实现 super() 的。但是在继承原生构造函数的情况下,A.call(this) 与 super() 在功能上是有区别的,ES5 中 A.call(this) 中的 this 是构造函数 B 的实例,也就是在实现实例属性继承上,ES5 是先创造构造函数 B 的实例,然后在让这个实例通过 A.call(this) 实现实例属性继承,在 ES6 中,是先新建父类的实例对象this,然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承。下面通过 2 段代码说明这个问题。

代码 1:

function MyArray() {
  Array.call(this);
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true,
    configurable: true
  }
});

var colors = new MyArray();
colors[0] = "red";
colors.length;

这段代码的思路就是,让构造函数 MyArray 继承原生构造函数 Array,然后验证 MyArray 的实例是否具有 Array 实例的特性。

代码 1 执行结果如下图:

代码 1 执行结果

从结果可以看出,MyArray 的实例并不具有 Array 实例的特性,之所以会发生这种情况,是因为 MyArray 的实例无法获得原生构造函数 Array 实例的内部属性,通过 Array.call(this) 也不行。

代码 2:

class MyArray extends Array {
  constructor() {
    super();
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length;

代码 2 执行结果如下图:

代码 2 执行结果

从结果可以看出,通过 super(),MyArray 的实例具有 Array 实例的特性。

4. 总结

ES6 与 ES5 中的继承有 2 个区别,第一个是,ES6 中子类会继承父类的属性,第二个区别是,super() 与 A.call(this) 是不同的,在继承原生构造函数的情况下,体现得很明显,ES6 中的子类实例可以继承原生构造函数实例的内部属性,而在 ES5 中做不到。