008-ES5 实现 ES6 类继承:深度解析与完整实现

ES5 实现 ES6 类继承:深度解析与完整实现

ES6 类继承回顾

ES6 类继承示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} makes a noise.`);
}

static info() {
console.log('Animal class');
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}

speak() {
super.speak();
console.log(`${this.name} barks!`);
}

static info() {
super.info();
console.log('Dog class');
}
}

const rex = new Dog('Rex', 'Labrador');
rex.speak();
// Rex makes a noise.
// Rex barks!

Dog.info();
// Animal class
// Dog class

ES5 实现 ES6 类继承

完整实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 1. 实现父类
function Animal(name) {
this.name = name;
}

// 2. 父类原型方法
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};

// 3. 父类静态方法
Animal.info = function() {
console.log('Animal class');
};

// 4. 实现子类继承
function Dog(name, breed) {
// 调用父类构造函数 (模拟 super(name))
Animal.call(this, name);
this.breed = breed;
}

// 5. 建立原型链 (关键步骤)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 6. 子类原型方法 (重写父类方法)
Dog.prototype.speak = function() {
// 调用父类方法 (模拟 super.speak())
Animal.prototype.speak.call(this);
console.log(this.name + ' barks!');
};

// 7. 继承静态方法 (ES6 类继承的静态方法也会被继承)
Object.setPrototypeOf(Dog, Animal);

// 8. 子类静态方法 (重写父类静态方法)
Dog.info = function() {
// 调用父类静态方法 (模拟 super.info())
Animal.info.call(this);
console.log('Dog class');
};

// 测试
var rex = new Dog('Rex', 'Labrador');
rex.speak();
// Rex makes a noise.
// Rex barks!

Dog.info();
// Animal class
// Dog class

关键继承技术详解

1. 原型链继承

1
2
3
4
5
6
// 基础实现
Child.prototype = new Parent();

// 问题:父类构造函数被调用两次(创建原型时和实例化时)
// 解决方案:使用 Object.create
Child.prototype = Object.create(Parent.prototype);

2. 构造函数继承

1
2
3
4
function Child() {
// 调用父类构造函数
Parent.call(this, arguments);
}

3. 组合继承(经典模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent(name) {
this.name = name;
}

Parent.prototype.say = function() {
console.log('Hello, ' + this.name);
};

function Child(name, age) {
Parent.call(this, name); // 第二次调用Parent
this.age = age;
}

Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;

// 问题:父类构造函数被调用两次

4. 寄生组合继承(最优方案)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function inherit(Child, Parent) {
// 创建原型对象
var prototype = Object.create(Parent.prototype);
// 修正构造函数指针
prototype.constructor = Child;
// 设置子类原型
Child.prototype = prototype;
// 设置静态继承
Object.setPrototypeOf(Child, Parent);
}

// 使用
function Child() {
Parent.apply(this, arguments);
}
inherit(Child, Parent);

各种继承方式对比

继承方式 优点 缺点 原型链 实例属性
原型链继承 简单 1. 引用类型共享问题
2. 无法传参
✅ ❌
构造函数继承 1. 解决引用类型问题
2. 可传参
无法继承原型方法 ❌ ✅
组合继承 1. 实例属性独立
2. 方法复用
父类构造函数调用两次 ✅ ✅
原型式继承 简单对象继承 引用类型共享问题 ✅ ❌
寄生式继承 增强对象 方法不能复用 ❌ ❌
寄生组合继承 最优方案(ES5实现ES6类继承) 实现稍复杂 ✅ ✅
ES6类继承 语法简洁 需要现代JS环境 ✅ ✅

特殊继承场景处理

1. 多继承实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 混入(Mixin)模式实现多继承
function mixin(target, ...sources) {
Object.assign(target.prototype, ...sources.map(src => src.prototype));
return target;
}

// 定义可混入的功能
const CanSwim = {
swim() {
console.log(`${this.name} is swimming`);
}
};

const CanFly = {
fly() {
console.log(`${this.name} is flying`);
}
};

// 基类
function Animal(name) {
this.name = name;
}

// 创建混合类
function Duck(name) {
Animal.call(this, name);
}

// 继承Animal
Duck.prototype = Object.create(Animal.prototype);
Duck.prototype.constructor = Duck;

// 混入功能
mixin(Duck, CanSwim, CanFly);

// 使用
const donald = new Duck('Donald');
donald.swim(); // Donald is swimming
donald.fly(); // Donald is flying

2. 静态属性和方法继承

1
2
3
4
5
6
7
8
9
10
11
function Parent() {}
Parent.staticProp = 'Parent Static';
Parent.staticMethod = function() {
console.log('Parent static method');
};

function Child() {}
Object.setPrototypeOf(Child, Parent);

console.log(Child.staticProp); // 'Parent Static'
Child.staticMethod(); // 'Parent static method'

3. 私有字段模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用WeakMap模拟私有字段
const privateData = new WeakMap();

class Animal {
constructor(name) {
privateData.set(this, { name });
}

getName() {
return privateData.get(this).name;
}
}

// ES5实现
function Animal(name) {
const privateFields = { name };

this.getName = function() {
return privateFields.name;
};
}

原型链关系图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
graph TD
A[rex: Dog实例] --> B[[__proto__]]
B --> C[Dog.prototype]
C --> D[[constructor]]
D --> E[Dog构造函数]
C --> F[[__proto__]]
F --> G[Animal.prototype]
G --> H[[constructor]]
H --> I[Animal构造函数]
G --> J[[__proto__]]
J --> K[Object.prototype]
K --> L[[__proto__]]
L --> M[null]

E --> N[[__proto__]]
N --> O[Animal构造函数]
O --> P[[__proto__]]
P --> Q[Function.prototype]

style A fill:#f9f,stroke:#333
style E fill:#bbf,stroke:#333
style I fill:#bbf,stroke:#333

继承的现代演进

1. ES6 类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} makes a noise.`);
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}

speak() {
super.speak();
console.log(`${this.name} barks!`);
}
}

2. 使用 Reflect 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent(name) {
this.name = name;
}

function Child(name, age) {
// 使用Reflect.construct优化
const instance = Reflect.construct(Parent, [name], new.target);
instance.age = age;
return instance;
}

// 设置原型链
Object.setPrototypeOf(Child, Parent);
Object.setPrototypeOf(Child.prototype, Parent.prototype);

const child = new Child('Alice', 10);
console.log(child instanceof Parent); // true

3. 使用 class 语法糖转译

Babel 将 ES6 类转译为 ES5 的代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ES6
class Child extends Parent {}

// Babel 转译后
function _inherits(subClass, superClass) {
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});
if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf ||
function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
return _setPrototypeOf(o, p);
}

var Child = (function(_Parent) {
_inherits(Child, _Parent);

function Child() {
_classCallCheck(this, Child);
return _Parent.apply(this, arguments) || this;
}

return Child;
})(Parent);

实际应用场景

1. UI 组件继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 基类:UI组件
function UIComponent() {
this.element = document.createElement('div');
}

UIComponent.prototype.render = function() {
return this.element;
};

// 按钮组件
function Button(text) {
UIComponent.call(this);
this.text = text;
this.element.textContent = text;
this.element.className = 'btn';
}

// 继承
Button.prototype = Object.create(UIComponent.prototype);
Button.prototype.constructor = Button;

// 添加新方法
Button.prototype.onClick = function(handler) {
this.element.addEventListener('click', handler);
};

// 使用
const myButton = new Button('Click Me');
document.body.appendChild(myButton.render());
myButton.onClick(() => alert('Clicked!'));

2. 数据模型继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 基类:数据库模型
function Model() {}
Model.prototype.save = function() {
console.log(`Saving ${this.constructor.name} to database`);
};

// 用户模型
function User(name, email) {
this.name = name;
this.email = email;
}

// 继承
User.prototype = Object.create(Model.prototype);
User.prototype.constructor = User;

// 添加用户特定方法
User.prototype.sendWelcomeEmail = function() {
console.log(`Sending welcome email to ${this.email}`);
};

// 使用
const user = new User('Alice', 'alice@example.com');
user.save(); // Saving User to database
user.sendWelcomeEmail(); // Sending welcome email to alice@example.com

面试重点问题解答

1. new 关键字执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
function myNew(Constructor, ...args) {
// 1. 创建空对象并链接原型
const obj = Object.create(Constructor.prototype);

// 2. 绑定this并执行构造函数
const result = Constructor.apply(obj, args);

// 3. 返回对象(如果构造函数返回对象则使用它)
return result instanceof Object ? result : obj;
}

// 使用
const rex = myNew(Dog, 'Rex', 'Labrador');

2. instanceof 原理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
function myInstanceof(obj, constructor) {
// 获取对象的原型
let proto = Object.getPrototypeOf(obj);

while (proto) {
// 找到原型匹配
if (proto === constructor.prototype) return true;
// 继续向上查找
proto = Object.getPrototypeOf(proto);
}

return false;
}

3. 为什么寄生组合继承是最佳方案?

  1. 效率高:只调用一次父类构造函数
  2. 原型链纯净:子类原型不包含父类实例属性
  3. 完整性:保留完整的原型链关系
  4. 无冗余:实例属性不会重复创建
  5. 支持静态继承:通过 Object.setPrototypeOf 实现

总结与最佳实践

继承选择指南

  1. 现代项目:优先使用 ES6 类继承
  2. 旧浏览器支持:使用寄生组合继承
  3. 功能混入:使用 Mixin 模式
  4. 私有字段:使用 WeakMap 或闭包

最佳实践原则

  1. 遵循 LSP 原则:子类应能替换父类
  2. 避免深度继承:继承层次不超过 3 层
  3. 多用组合少用继承:优先考虑对象组合
  4. 封装变化:将变化点封装在子类中
  5. 保持一致性:子类不应改变父类行为
1
2
3
4
5
6
7
graph LR
A[继承需求] --> B{选择方案}
B -->|ES6环境| C[ES6类继承]
B -->|ES5环境| D[寄生组合继承]
B -->|多继承需求| E[Mixin模式]
B -->|共享方法| F[原型链继承]
B -->|实例隔离| G[构造函数继承]