ES6怎么实现继承(class +extends)

78 阅读7分钟

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(原型链关联)

二、核心关键字解析(constructorsuperstatic

ES6 继承的关键是理解三个核心关键字,它们直接决定了继承的逻辑:

1. constructor:构造函数

  • 作用:初始化实例属性(如 nameage),相当于 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.静态方法名 调用);
  • 注意:静态方法中无法访问 thisthis 指向类本身,而非实例),只能访问静态属性。

示例(静态属性与静态方法):

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 内置对象(如 ArrayDate),解决了 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()

❌ 错误:继承 ArrayDate 等内置对象时,必须调用 super() 初始化内置对象的内部状态:

class MyArray extends Array {
  constructor() {
    // 未调用 super(),报错
  }
}

✅ 正确:

class MyArray extends Array {
  constructor(...items) {
    super(...items); // 调用 Array 的构造函数
  }
}

总结

ES6 继承的核心是 class + extends,配合 constructor 和 super 实现简洁、直观的继承逻辑:

  1. 用 class 定义类,extends 声明继承关系;
  2. 子类 constructor 必须调用 super(),初始化父类属性并绑定 this
  3. 原型方法、静态方法的继承的继承都原生支持,无需手动处理;
  4. 底层仍是原型链机制,兼容 ES5 的继承逻辑,但语法更简洁、不易出错。

现在开发中,除非需要兼容 IE 等旧浏览器,否则优先使用 ES6 继承(class extends),它能大幅提升代码的可读性和维护性,是 JavaScript 面向对象编程的主流方式。