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[构造函数继承]