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));
}