ES5 与 ES6 继承的区别(除写法外)

1. 继承机制不同

  • ES5 的继承:实质上是先创建子类的实例对象 this,然后再将父类的方法添加到 this 上(通过 Parent.call(this))。
  • ES6 的继承:实质上是先创建父类的实例对象 this(所以必须先调用父类的 super() 方法),然后再用子类的构造函数修改 this。

2. this 与 super

ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例会报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工;如果不调用 super,子类就拿不到 this 对象。

super 关键字指代父类的实例(即父类的 this 对象)。在子类构造函数中,调用 super 后才可使用 this 关键字,否则报错。

3. 对比示例

// ES5 寄生组合式继承
function Parent(name) { this.name = name; }
Parent.prototype.say = function () { console.log(this.name); };

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// ES6 class extends
class Parent {
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
}
class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}

4. ES6 class 自身的特性差异

除继承机制外,ES6 class 相比 ES5 构造函数还有以下语义差异:

4.1 不提升、暂时性死区

class 声明会提升,但不会初始化赋值,进入暂时性死区(TDZ),行为类似 let / const。

4.2 默认严格模式

class 声明内部会自动启用严格模式。

4.3 所有方法不可枚举

class 的所有方法(包括静态方法和实例方法)都是不可枚举的;而 ES5 中挂在 prototype 上的方法默认是可枚举的。

function Bar() {
  this.bar = 42;
}
Bar.answer = function() {
  return 42;
};
Bar.prototype.print = function() {
  console.log(this.bar);
};
const barKeys = Object.keys(Bar);        // ['answer']
const barProtoKeys = Object.keys(Bar.prototype); // ['print']

class Foo {
  constructor() {
    this.foo = 42;
  }
  static answer() {
    return 42;
  }
  print() {
    console.log(this.foo);
  }
}
const fooKeys = Object.keys(Foo);        // []
const fooProtoKeys = Object.keys(Foo.prototype); // []

4.4 方法没有原型对象 prototype

class 的所有方法都没有原型对象 prototype,所以也没有 [[construct]],不能使用 new 来调用。

function Bar() {
  this.bar = 42;
}
const bar = Bar(); // it's ok

class Foo {
  constructor() {
    this.foo = 42;
  }
}
const foo = Foo();
// TypeError: Class constructor Foo cannot be invoked without 'new'

4.5 不能在内部重写类名

class 内部无法重写类名(构造函数中 ClassName = ... 会抛 TypeError: Assignment to constant variable);函数则可以。

function Bar() {
  Bar = "Baz"; // it's ok
  this.bar = 42;
}
const bar = new Bar();
// Bar: 'Baz'
// bar: Bar {bar: 42}

class Foo {
  constructor() {
    this.foo = 42;
    Foo = "Fol"; // TypeError: Assignment to constant variable
  }
}
const foo = new Foo();
Foo = "Fol"; // it's ok(外部仍可改写)

5. 其他细节差异

  • ES6 class 是 ES5 继承的语法糖,底层仍是原型链,但写法更清晰。
  • ES6 继承通过 Object.setPrototypeOf 把子类原型链指向父类,避免了 ES5 中多次调用父类构造的问题。
  • ES6 class 内定义的方法不可枚举(与 ES5 不同),更符合类的语义。
  • ES6 支持静态方法的继承(static 关键字)。

来源整理自:vue3js.cn 面试官系列