ES6怎么实现继承
ES6 继承实现详解:class + extends 语法糖与底层原理
ES6 引入了 class 关键字和 extends 语法,彻底简化了继承实现 —— 无需手动操作原型链(prototype)和构造函数,即可直观地实现类继承。但本质上,ES6 继承是 ES5 寄生组合继承的语法糖,底层仍依赖原型链和构造函数机制,只是屏蔽了复杂的底层细节。
以下从 核心语法、完整实现、底层原理、高级特性 四个维度,详解 ES6 继承的实现方式。
一、核心语法(基础继承)
ES6 继承的核心是 class(定义类)和 extends(声明继承关系),配合 constructor(构造函数)和 super(调用父类方法),即可完成基础继承。
1. 基础实现代码
// 父类(基类):用 class 定义
class Parent {
// 构造函数:初始化实例属性(等价于 ES5 的构造函数)
constructor(name) {
this.name = name; // 父类实例属性
this.colors = ['red', 'blue']; // 父类引用类型属性
}
// 原型方法(等价于 ES5 的 Parent.prototype.sayName)
sayName() {
console.log(`父类名字:${this.name}`);
}
// 静态方法(用 static 修饰,属于类本身,而非实例)
static sayHello() {
console.log('父类静态方法:Hello');
}
}
// 子类:用 extends 继承 Parent
class Child extends Parent {
// 构造函数:初始化子类实例属性
constructor(name, age) {
// 核心:必须先调用 super(),否则 this 未初始化(报错)
super(name); // 等价于 ES5 的 Parent.call(this, name),向父类传参
this.age = age; // 子类实例属性(必须在 super 之后定义)
}
// 子类原型方法(覆盖/扩展父类方法)
sayAge() {
console.log(`子类年龄:${this.age}`);
}
// 子类静态方法
static sayHi() {
// 静态方法中用 super 调用父类静态方法
super.sayHello();
console.log('子类静态方法:Hi');
}
}
2. 测试用例(验证继承效果)
// 1. 实例化子类
const child1 = new Child('小花', 2);
const child2 = new Child('小黑', 3);
// 2. 访问父类实例属性(无共享,每个实例独立)
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue'](解决引用共享问题)
// 3. 调用父类原型方法
child1.sayName(); // 父类名字:小花
// 4. 调用子类原型方法
child1.sayAge(); // 子类年龄:2
// 5. 调用父类静态方法(通过子类调用)
Child.sayHello(); // 父类静态方法:Hello
// 6. 调用子类静态方法(内部调用父类静态方法)
Child.sayHi(); // 父类静态方法:Hello → 子类静态方法:Hi
// 7. 原型链验证(子类实例既是子类也是父类的实例)
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true
console.log(Child instanceof Parent); // false(类本身不是父类实例)
console.log(Child.prototype.__proto__ === Parent.prototype); // true(原型链关联)
二、核心关键字解析(constructor、super、static)
ES6 继承的关键是理解三个核心关键字,它们直接决定了继承的逻辑:
1. constructor:构造函数
- 作用:初始化实例属性(如
name、age),相当于 ES5 中定义的构造函数; - 子类必须调用
super():子类的constructor中,this需通过super()初始化(继承父类的this),否则报错; - 省略
constructor的情况:若子类无需额外初始化属性,可省略constructor,ES6 会自动生成默认构造函数(constructor(...args) { super(...args); }),自动向父类传参。
示例(省略 constructor):
class Child extends Parent {
// 无 constructor,自动生成默认构造函数
sayAge() {
console.log(`年龄:${this.age}`); // 此处 this 由 super() 初始化
}
}
const child = new Child('小白'); // 自动调用 super('小白')
child.sayName(); // 父类名字:小白
2. super:父类引用
super 有两种用法,取决于使用场景:
(1)构造函数中:super(参数)
- 作用:调用父类的构造函数(等价于 ES5 的
Parent.call(this, 参数)); - 必须在子类
constructor的第一行(否则this未定义); - 可向父类传递参数(如
super(name)传递name给Parent)。
(2)原型方法 / 静态方法中:super.方法名()
- 原型方法中:
super指向父类的原型(Parent.prototype),用于调用父类的原型方法; - 静态方法中:
super指向父类本身(Parent),用于调用父类的静态方法。
示例(super 调用父类方法):
class Child extends Parent {
// 原型方法中调用父类原型方法
sayName() {
super.sayName(); // 调用父类的 sayName
console.log(`子类重写:${this.name}`);
}
// 静态方法中调用父类静态方法
static sayHi() {
super.sayHello(); // 调用父类的 sayHello
}
}
const child = new Child('小花');
child.sayName(); // 父类名字:小花 → 子类重写:小花
Child.sayHi(); // 父类静态方法:Hello
3. static:静态方法
- 作用:定义属于「类本身」的方法,而非实例方法(实例无法调用,只能通过类调用);
- 继承规则:子类会继承父类的静态方法(通过
Child.静态方法名调用); - 注意:静态方法中无法访问
this(this指向类本身,而非实例),只能访问静态属性。
示例(静态属性与静态方法):
class Parent {
static count = 0; // 静态属性(ES6+ 支持类字段声明)
static increment() {
Parent.count++; // 访问静态属性
}
}
class Child extends Parent {}
Parent.increment();
console.log(Parent.count); // 1
Child.increment();
console.log(Parent.count); // 2(子类调用父类静态方法,修改父类静态属性)
三、ES6 继承的底层原理(语法糖的本质)
ES6 class extends 本质是 ES5 寄生组合继承的语法糖,底层逻辑完全一致,只是语法更简洁。我们可以通过 ES5 代码还原其底层实现:
底层逻辑还原(等价 ES5 代码)
// 父类(ES5 写法)
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(`父类名字:${this.name}`);
};
Parent.sayHello = function() {
console.log('父类静态方法:Hello');
};
// 子类(ES5 寄生组合继承,等价于 ES6 的 class Child extends Parent)
function Child(name, age) {
// 对应 ES6 的 super(name)
Parent.call(this, name);
this.age = age;
}
// 1. 原型链关联(对应 ES6 的 extends 原型继承)
Child.prototype = Object.create(Parent.prototype);
// 2. 修复 constructor 指向
Child.prototype.constructor = Child;
// 3. 子类原型方法(对应 ES6 的 sayAge)
Child.prototype.sayAge = function() {
console.log(`子类年龄:${this.age}`);
};
// 4. 子类静态方法(对应 ES6 的 static sayHi)
Child.sayHi = function() {
Parent.sayHello();
console.log('子类静态方法:Hi');
};
核心对应关系(ES6 vs ES5)
| ES6 语法 | 等价 ES5 逻辑 |
|---|---|
class Parent {} | function Parent() {}(构造函数)+ 原型方法 |
class Child extends Parent | 寄生组合继承(Object.create + Parent.call) |
constructor(name) { super(name); } | Parent.call(this, name)(调用父类构造函数) |
sayName() {} | Child.prototype.sayName = function() {} |
static sayHello() {} | Child.sayHello = function() {} |
super.sayName() | Parent.prototype.sayName.call(this) |
super.sayHello() | Parent.sayHello.call(this) |
结论:ES6 继承没有改变 JavaScript 基于原型链的继承本质,只是用更接近传统面向对象(如 Java)的语法,降低了使用门槛。
四、高级特性(完善的继承能力)
ES6 继承支持更多灵活的高级特性,满足复杂场景需求:
1. 方法重写(覆盖父类方法)
子类可以定义与父类同名的方法,覆盖父类的实现,同时通过 super 调用父类方法:
class Parent {
eat() {
console.log('父类:吃饭');
}
}
class Child extends Parent {
eat() {
super.eat(); // 先调用父类方法
console.log('子类:吃零食'); // 再扩展自己的逻辑
}
}
const child = new Child();
child.eat(); // 父类:吃饭 → 子类:吃零食
2. 继承内置对象(如 Array、Date)
ES6 允许子类继承 JavaScript 内置对象(如 Array、Date),解决了 ES5 中无法正常继承内置对象的问题:
// 子类继承 Array,实现自定义数组
class MyArray extends Array {
// 重写 push 方法,添加日志
push(...items) {
console.log(`添加了 ${items.length} 个元素`);
super.push(...items); // 调用父类 Array 的 push 方法
}
// 自定义方法:求数组总和
sum() {
return this.reduce((a, b) => a + b, 0);
}
}
const arr = new MyArray(1, 2, 3);
arr.push(4, 5); // 添加了 2 个元素
console.log(arr); // MyArray(5) [1, 2, 3, 4, 5]
console.log(arr.sum()); // 15(自定义方法)
console.log(arr instanceof Array); // true(仍是 Array 实例)
3. 多重继承(通过 extends 实现间接多重继承)
ES6 不支持直接多重继承(如 class Child extends Parent1, Parent2),但可通过「中间类」或「混入(Mixin)」实现间接多重继承:
// 混入类 1:可飞行
const Flyable = {
fly() {
console.log('会飞');
}
};
// 混入类 2:可游泳
const Swimmable = {
swim() {
console.log('会游泳');
}
};
// 父类
class Animal {}
// 子类:继承 Animal,同时混入 Flyable 和 Swimmable
class Bird extends Animal {
constructor() {
super();
// 混入:将 Flyable 和 Swimmable 的方法添加到实例
Object.assign(this, Flyable, Swimmable);
}
}
const bird = new Bird();
bird.fly(); // 会飞
bird.swim(); // 会游泳
4. 类字段声明(ES6+ 扩展)
ES6 后续扩展了「类字段声明」语法,支持直接在类中定义实例属性和静态属性(无需在 constructor 中):
class Parent {
// 实例属性(直接声明,等价于在 constructor 中 this.name = '默认名')
name = '默认名';
// 静态属性
static type = '动物';
constructor() {
// 可覆盖默认实例属性
// this.name = '自定义名';
}
}
const parent = new Parent();
console.log(parent.name); // 默认名
console.log(Parent.type); // 动物
五、ES6 继承 vs ES5 继承(核心差异)
| 特性 | ES6 继承 | ES5 继承(寄生组合继承) |
|---|---|---|
| 语法简洁性 | 高(class + extends 直观) | 低(需手动操作原型链 + 构造函数) |
| 构造函数调用 | 自动处理(super 统一调用) | 需手动调用 Parent.call(this) |
constructor 修复 | 自动修复(无需手动处理) | 需手动 Child.prototype.constructor = Child |
| 继承内置对象 | 支持(如 class MyArray extends Array) | 不支持(原型链机制限制) |
| 静态方法继承 | 原生支持(static 关键字) | 需手动复制父类静态方法(Child.sayHello = Parent.sayHello) |
| 可读性 | 高(接近传统 OOP 语法) | 低(依赖原型链知识,易出错) |
核心结论:ES6 继承是 ES5 寄生组合继承的 “语法糖”,解决了 ES5 继承的繁琐和易出错问题,同时保留了原型链的灵活性,是现在 JavaScript 继承的首选方式。
六、常见误区(避坑指南)
1. 子类 constructor 中未调用 super()
❌ 错误:
class Child extends Parent {
constructor(name, age) {
this.age = age; // 报错:Must call super constructor in derived class before accessing 'this'
}
}
✅ 正确:必须先调用 super(),再访问 this:
class Child extends Parent {
constructor(name, age) {
super(name); // 先调用 super
this.age = age; // 后定义子类属性
}
}
2. 静态方法中访问实例属性
❌ 错误:静态方法属于类,而非实例,无法访问 this 指向的实例属性:
class Parent {
static sayName() {
console.log(this.name); // undefined(this 指向 Parent 类,无 name 属性)
}
}
✅ 正确:静态方法只能访问静态属性或接收参数:
class Parent {
static name = '父类';
static sayName() {
console.log(this.name); // 父类(访问静态属性)
}
}
3. 继承内置对象时重写 constructor 未调用 super()
❌ 错误:继承 Array、Date 等内置对象时,必须调用 super() 初始化内置对象的内部状态:
class MyArray extends Array {
constructor() {
// 未调用 super(),报错
}
}
✅ 正确:
class MyArray extends Array {
constructor(...items) {
super(...items); // 调用 Array 的构造函数
}
}
总结
ES6 继承的核心是 class + extends,配合 constructor 和 super 实现简洁、直观的继承逻辑:
- 用
class定义类,extends声明继承关系; - 子类
constructor必须调用super(),初始化父类属性并绑定this; - 原型方法、静态方法的继承的继承都原生支持,无需手动处理;
- 底层仍是原型链机制,兼容 ES5 的继承逻辑,但语法更简洁、不易出错。
现在开发中,除非需要兼容 IE 等旧浏览器,否则优先使用 ES6 继承(class extends),它能大幅提升代码的可读性和维护性,是 JavaScript 面向对象编程的主流方式。