深入解析JavaScript继承:ES5与ES6的对比与演进
在JavaScript中,继承是实现代码复用和抽象的核心机制之一。随着ES6(ECMAScript 2015)的推出,类(class和extends)彻底改变了实现继承的方式。本文将通过对比ES5和ES6的继承实现,揭示其底层原理与核心差异。
一、ES5的继承:基于原型链的手动实现
1. 核心机制
ES5的继承依赖于原型链和构造函数的组合,需要开发者手动操作原型对象。其实现分为两步:
- 继承属性:在子类构造函数中调用父类构造函数(
Parent.call(this)),将父类的属性绑定到子类实例。 - 继承方法:通过原型链(
Child.prototype = Object.create(Parent.prototype))让子类实例共享父类方法。
2. 代码示例
// 父类 function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(this.name + " makes a noise."); }; // 子类 function Dog(name, breed) { Animal.call(this, name); // 继承属性 this.breed = breed; } Dog.prototype = Object.create(Animal.prototype); // 继承方法 Dog.prototype.constructor = Dog; // 修复构造函数指向 Dog.prototype.bark = function() { console.log(this.name + " barks!"); }; // 使用 const dog = new Dog("Buddy", "Golden Retriever"); dog.speak(); // "Buddy makes a noise." dog.bark(); // "Buddy barks!"
3. 缺陷与问题
- 冗余属性:父类构造函数被调用两次(
Animal.call(this)和原型链设置),导致子类原型上可能存在冗余属性。 - 繁琐的手动操作:需要手动维护原型链和构造函数指向,容易出错。
- 静态方法不继承:父类的静态方法需手动绑定到子类。
二、ES6的继承:基于class和extends的语法糖
1. 核心机制
ES6通过class和extends关键字提供了一种更简洁的继承方式,其底层仍基于原型链,但隐藏了复杂的实现细节:
- 继承属性:通过
super()调用父类构造函数,初始化子类实例上的父类属性。 - 继承方法:通过
extends关键字自动建立原型链,继承父类的方法。自动设置子类的原型链(Child.prototype.__proto__ === Parent.prototype)。
2. 代码示例
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // 必须调用super()! this.breed = breed; } bark() { console.log(`${this.name} barks!`); } } // 使用 const dog = new Dog("Buddy", "Golden Retriever"); dog.speak(); // "Buddy makes a noise." dog.bark(); // "Buddy barks!"
3. 关键特性
- 强制调用
super():子类构造函数必须调用super()后才能使用this,因为子类实例的创建依赖父类构造函数的初始化。 - 自动继承静态方法:父类的静态方法(如
Parent.staticMethod())会被子类自动继承。 - 不可枚举的方法:类中定义的方法默认不可枚举(
Object.keys(Child.prototype)不会包含它们)。
三、ES5与ES6继承的核心差异
| 特性 | ES5 | ES6 |
|---|---|---|
| 实例创建顺序 | 子类实例在调用构造函数时直接创建 | 子类实例由父类构造函数初始化(通过 super()) |
| 属性继承 | 通过 Parent.call(this) 显式绑定到子类实例 |
通过 super() 隐式初始化父类属性 |
| 方法继承 | 手动设置原型链 | 自动通过 extends 设置原型链 |
| 静态方法继承 | 需手动绑定(如 Child.__proto__ = Parent) |
自动继承 |
四、ES6继承的底层原理
ES6的class看似引入了传统面向对象语言的类机制,但其本质仍是基于原型的继承。以下代码揭示了extends的底层行为:
class Parent {} class Child extends Parent {} // 原型链关系 console.log(Child.prototype.__proto__ === Parent.prototype); // true console.log(Child.__proto__ === Parent); // true(继承静态方法)
super()的作用:
调用父类构造函数,其内部this指向子类实例。因此,super()并非创建父类实例,而是为子类实例初始化父类属性。
五、总结
ES5 继承:
-
创建子类实例:首先,通过子类的构造函数创建实例。
-
绑定父类属性:在子类构造函数中,通过调用父类构造函数(通常使用
call或apply方法),将父类的属性绑定到新创建的子类实例上,从而实现属性的继承。 -
原型链继承方法:子类通过原型链(
Child.prototype = new Parent()或更优的Object.create(Parent.prototype))来访问父类原型上的方法,从而实现方法的继承。
ES6 继承:
-
初始化
this:在子类的constructor中,必须先调用super()。这个操作相当于调用了父类的构造函数,它会负责初始化子类实例的this对象。 -
加工
this:只有在super()调用之后,你才能在子类构造函数中访问或修改this对象。这时,你可以为this添加子类特有的属性和方法。 -
语法糖机制:
class和extends这两个关键字提供了更清晰的语法。它们在底层自动处理了原型链的设置(例如Child.prototype.__proto__指向Parent.prototype),以及静态方法的继承,大大简化了ES5中复杂的继承逻辑。

浙公网安备 33010602011771号