# 了解一下 JS 浅/深克隆??
# 为什么需要克隆,直接赋值不就好了?
js 有两种大类的类型:基本类型(undefined、Null、Boolean、Number 和 String)和引用类型(Object、Array 和 Function),基本类型的复制是不会产生深度复制和浅度复制问题,当使用引用类型进行复制的时候,其实复制的是指向内存值的指针,并没有真实复制其数据(浅复制)。
# 那什么是浅克隆深克隆呢?
浅克隆就是将栈内存中的引用复制一份,赋给一个新的变量,本质上两个指向堆内存中的同一地址,内容也相同,其中一个变化另一个内容也会变化(根本上改变的是同一个对象)。
深克隆就是创建一个新的对象,开辟一块内存,然后将原对象中的数据全部复制过去,新对象和原对象毫无关系,互不影响。
# 如何实现浅克隆?
# 方式一:扩展运算符
const obj1 = {
...obj
}
console.log('obj1', obj1)
1
2
3
4
2
3
4
# 方式二:混入
const obj2 = Object.assign({}, obj)
console.log('obj2', obj2)
1
2
2
当然,还有很多方法,如遍历对象的 key 逐一赋值,这里就不举例了
# 如何实现深克隆?
# 方式一:JSON 转换
const obj3 = JSON.parse(JSON.stringify(obj))
console.log('obj3', obj3)
1
2
2
JSON 转换可以解决大部分场景,但是它有一些不足:
- 无法实现对函数、RegExp 等特殊对象的克隆。
- 会抛弃对象的 constructor,所有的构造函数都会指向 object
- 对象有循环引用,会报错
# 方式二:MessageChannel
// 解决循环引用和undefined,但不能克隆函数和Symbol
function clone_message_channel(obj) {
return new Promise((resolve, reject) => {
const message_channel = new MessageChannel()
console.log('returnnewPromise ~ message_channel', message_channel)
const { port1, port2 } = message_channel
port2.postMessage(obj)
port1.onmessage = (ev) => resolve(ev.data)
})
}
// 如何使用
const obj4 = async () => {
const clone = await clone_message_channel(obj)
console.log('obj4 ~ clone', clone)
}
obj4()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
同样,messageChannel 虽然解决了循环引用和 undefined 的问题,但是它不能克隆函数和 Symbol
# 方式三:手写
function deep_clone(obj) {
const map = new WeakMap()
const _clone = (obj) => {
if (obj instanceof Object) {
if (map.has(obj)) {
// 处理循环引用
return map.get(obj)
}
let newObj = undefined
if (obj instanceof Function) {
// 处理函数
newObj = function() {
return obj.apply(this, arguments)
}
} else if (obj instanceof Array) {
// 处理数组
newObj = []
} else if (obj instanceof RegExp) {
// 处理正则
newObj = new RegExp(obj.source, obj.flags)
} else if (obj instanceof Date) {
// 处理Date类
newObj = new Date(obj)
} else {
// 处理普通对象
newObj = {}
}
// 处理原型
newObj.__proto__ = Object.getPrototypeOf(obj)
// 相当于浅克隆一个对象
const desc = Object.getOwnPropertyDescriptors(obj)
const clone = Object.create(Object.getPrototypeOf(obj), desc)
map.set(obj, clone)
for (const key of Reflect.ownKeys(obj)) {
//key为Symbol值
if (obj.hasOwnProperty(key)) {
newObj[key] = _clone(obj[key])
}
}
return newObj
}
// 原始值、Symbol
return obj
}
return _clone(obj)
}
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
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