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

009-深拷贝与浅拷贝原理+手撕

深拷贝与浅拷贝:原理、实现与最佳实践

核心概念解析

浅拷贝(Shallow Copy)

  • 定义:创建一个新对象,新对象的属性值是对原对象属性值的引用
  • 特点:
    • 只复制第一层属性
    • 嵌套对象与原对象共享内存
    • 修改嵌套对象会影响原对象
  • 内存示意图:
    原始对象: { a: 1, b: { c: 2 } }
    浅拷贝对象: { a: 1, b: [引用地址] }
    ↑
    原始对象的b属性 ←┘

深拷贝(Deep Copy)

  • 定义:创建一个完全独立的新对象,递归复制所有层级的属性
  • 特点:
    • 复制对象的所有层级
    • 新对象与原对象完全隔离
    • 修改新对象不会影响原对象
  • 内存示意图:
    原始对象: { a: 1, b: { c: 2 } }
    深拷贝对象: { a: 1, b: { c: 2 } } // 全新的内存地址

常用方法对比

浅拷贝方法

方法 示例 说明
扩展运算符 const copy = { ...obj } ES6+ 推荐方式
Object.assign() const copy = Object.assign({}, obj) 合并对象实现浅拷贝
Array.prototype.slice() const copy = arr.slice() 数组浅拷贝
Array.from() const copy = Array.from(arr) 类数组转数组实现浅拷贝
手动遍历 见下方实现 自定义浅拷贝逻辑

深拷贝方法

方法 示例 优点 缺点
JSON.parse/stringify JSON.parse(JSON.stringify(obj)) 简单快速 丢失函数/Symbol/undefined/循环引用等
Lodash.cloneDeep _.cloneDeep(obj) 功能完整,处理各种边界情况 需引入外部库
结构化克隆 structuredClone(obj) 浏览器原生API,支持循环引用 Node环境不支持,不支持函数/原型链
手动递归实现 见下方实现 完全可控,支持自定义处理 实现复杂度高

手写实现

浅拷贝函数实现

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
function shallowCopy(source) {
// 处理非对象类型
if (source === null || typeof source !== 'object') {
return source;
}

// 处理数组
if (Array.isArray(source)) {
return [...source];
}

// 处理普通对象
const copy = {};
for (const key in source) {
// 只复制自身属性(非原型链属性)
if (source.hasOwnProperty(key)) {
copy[key] = source[key];
}
}

return copy;
}

// 测试用例
const original = { a: 1, b: { c: 2 } };
const copy = shallowCopy(original);

console.log(copy.b === original.b); // true(共享嵌套对象)

深拷贝函数实现(基础版)

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
function deepCopy(source) {
// 基本类型直接返回
if (source === null || typeof source !== 'object') {
return source;
}

// 处理数组
if (Array.isArray(source)) {
return source.map(item => deepCopy(item));
}

// 处理普通对象
const copy = {};
for (const key in source) {
if (source.hasOwnProperty(key)) {
copy[key] = deepCopy(source[key]);
}
}

return copy;
}

// 测试用例
const original = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /regex/g,
f: function() { console.log('hello') }
};

const copy = deepCopy(original);

console.log(copy.b === original.b); // false(独立嵌套对象)
console.log(copy.d instanceof Date); // true
console.log(copy.e instanceof RegExp); // true
console.log(typeof copy.f); // 'function'

深拷贝函数实现(进阶版)

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function deepCopyAdvanced(source, map = new WeakMap()) {
// 基本类型直接返回
if (source === null || typeof source !== 'object') {
return source;
}

// 处理循环引用
if (map.has(source)) {
return map.get(source);
}

// 处理特殊对象
switch (true) {
case source instanceof Date:
return new Date(source);

case source instanceof RegExp:
return new RegExp(source);

case source instanceof Map:
return new Map(Array.from(source, ([key, val]) =>
[deepCopyAdvanced(key, map), deepCopyAdvanced(val, map)]
);

case source instanceof Set:
return new Set(Array.from(source, item =>
deepCopyAdvanced(item, map))
);

case Array.isArray(source):
const arrCopy = [];
map.set(source, arrCopy);
source.forEach((item, index) => {
arrCopy[index] = deepCopyAdvanced(item, map);
});
return arrCopy;

default:
// 处理普通对象和类实例
const objCopy = Object.create(
Object.getPrototypeOf(source)
);
map.set(source, objCopy);

// 复制Symbol属性
const symbolKeys = Object.getOwnPropertySymbols(source);
const allKeys = [...Object.keys(source), ...symbolKeys];

allKeys.forEach(key => {
objCopy[key] = deepCopyAdvanced(source[key], map);
});

return objCopy;
}
}

// 测试用例
const original = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /regex/g,
f: function() { return this.a; },
g: new Map([['key', 'value']]),
h: new Set([1, 2, 3]),
i: Symbol('unique'),
j: [4, 5, { k: 6 }]
};

// 添加循环引用
original.self = original;
original.j.push(original.j);

const copy = deepCopyAdvanced(original);

// 验证
console.log(copy !== original); // true
console.log(copy.b !== original.b); // true
console.log(copy.d instanceof Date); // true
console.log(copy.e instanceof RegExp); // true
console.log(copy.f()); // 1(函数正常执行)
console.log(copy.g instanceof Map); // true
console.log(copy.h instanceof Set); // true
console.log(copy.i === original.i); // true(Symbol相同)
console.log(copy.self === copy); // true(循环引用保持)
console.log(copy.j[3] === copy.j); // true(循环引用保持)

特殊场景处理

1. 函数拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数拷贝策略
if (typeof source === 'function') {
// 方案1:直接引用(共享函数)
// return source;

// 方案2:创建新函数(保留this绑定)
return function(...args) {
return source.apply(this, args);
};

// 方案3:完整克隆(复杂)
// const fnStr = source.toString();
// return new Function('return ' + fnStr)();
}

2. 原型链处理

1
2
3
4
// 保持原型链
const objCopy = Object.create(
Object.getPrototypeOf(source)
);

3. 不可枚举属性

1
2
3
4
5
6
7
8
9
// 复制不可枚举属性
const props = Object.getOwnPropertyDescriptors(source);
for (const key of Reflect.ownKeys(props)) {
Object.defineProperty(
copy,
key,
deepCopyAdvanced(props[key], map)
);
}

4. 循环引用检测

1
2
3
4
5
6
7
8
// 使用WeakMap检测循环引用
const map = new WeakMap();

if (map.has(source)) {
return map.get(source);
}

map.set(source, copy);

性能优化策略

1. 减少递归层级

1
2
3
4
// 扁平化数据结构处理
if (depth > MAX_DEPTH) {
return Array.isArray(source) ? [...source] : {...source};
}

2. 特殊对象优化

1
2
3
4
5
6
7
8
9
10
11
12
// TypedArray高效处理
if (source instanceof ArrayBuffer) {
return source.slice(0);
}

if (ArrayBuffer.isView(source)) {
return new source.constructor(
source.buffer.slice(0),
source.byteOffset,
source.byteLength / source.BYTES_PER_ELEMENT
);
}

3. 缓存机制

1
2
3
4
5
6
7
8
9
10
11
12
// 缓存已处理对象
const cache = new WeakMap();

function cachedDeepCopy(source) {
if (cache.has(source)) {
return cache.get(source);
}

const copy = deepCopyAdvanced(source);
cache.set(source, copy);
return copy;
}

应用场景分析

适合浅拷贝的场景

场景 原因 示例
配置对象合并 只需顶层属性覆盖 Object.assign(config, overrides)
状态快照(简单状态) 状态对象无嵌套结构 Vue的data对象
函数参数传递 避免修改原始对象 function process(data) { const local = {...data} }
数组切片操作 只需部分数组元素 const page = items.slice(0, 10)

适合深拷贝的场景

场景 原因 示例
状态管理(Redux) 需要完整不可变状态树 Redux reducer中的状态更新
复杂对象序列化 需要完整对象结构 保存到本地存储的复杂状态
避免污染原始数据 操作数据副本不影响源数据 表格编辑临时副本
跨线程通信(Web Worker) 需要序列化所有数据 worker.postMessage(structuredClone(data))

现代API:structuredClone

浏览器原生深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const original = { 
a: 1,
b: { c: 2 },
d: new Date(),
e: new Set([1, 2, 3])
};

try {
const copy = structuredClone(original);
console.log(copy.b !== original.b); // true
console.log(copy.d instanceof Date); // true
} catch (err) {
console.error('不支持structuredClone', err);
}

支持特性

类型 支持 说明
基本类型 ✅ number, string, boolean等
Array/Object ✅ 普通对象和数组
Date ✅
RegExp ✅
Map/Set ✅
ArrayBuffer ✅
Blob/File ✅
ImageData ✅
函数 ❌ 抛出DataCloneError异常
DOM节点 ❌ 抛出DataCloneError异常
原型链 ❌ 只复制自身属性
循环引用 ✅ 正确处理循环引用

总结与最佳实践

选择策略

1
2
3
4
5
6
7
8
9
10
graph TD
A[需要拷贝] --> B{是否有嵌套对象}
B -->|是| C{需要完全独立}
B -->|否| D[使用浅拷贝]
C -->|是| E[使用深拷贝]
C -->|否| D
E --> F{环境支持}
F -->|现代浏览器| G[structuredClone]
F -->|Node/复杂需求| H[Lodash.cloneDeep]
F -->|自定义需求| I[手写深拷贝]

最佳实践

  1. 优先使用原生API:

    • 浅拷贝:扩展运算符 {...obj}
    • 深拷贝:structuredClone(浏览器环境)
  2. 复杂场景用成熟库:

    1
    2
    import { cloneDeep } from 'lodash-es';
    const copy = cloneDeep(original);
  3. 避免JSON暴力转换:

    1
    2
    // 不推荐 - 丢失类型信息
    const copy = JSON.parse(JSON.stringify(original));
  4. 循环引用要处理:

    • 手动实现时使用WeakMap跟踪
    • 使用库函数自动处理
  5. 性能敏感场景注意:

    • 大对象深拷贝可能阻塞主线程
    • 考虑增量复制或Web Worker

终极方案推荐

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
/**
* 生产环境推荐方案
* 1. 浏览器环境:使用structuredClone
* 2. Node环境:使用Lodash的cloneDeep
* 3. 特殊需求:自定义深拷贝函数
*/

function safeDeepCopy(obj) {
// 浏览器环境使用原生API
if (typeof structuredClone === 'function') {
return structuredClone(obj);
}

// Node环境使用Lodash
if (typeof require === 'function') {
try {
const { cloneDeep } = require('lodash');
return cloneDeep(obj);
} catch (e) {
// 降级处理
}
}

// 降级方案:基础深拷贝
return JSON.parse(JSON.stringify(obj));
}