各位IT界的彦祖,神仙姐姐们中午好。
前提
克隆方式在日常开发中大家都还是使用较多的,毕竟像数组、对象都是引用类型,直接使用一不小心就会造成意料之外的bug。那么今天我们来分享一下对象的克隆方式通常有哪些。
正文
回忆一下,大家在日常开发时需要克隆的时候通常使用什么方式呢,有人会说使用进行转换JSON.parse(JSON.stringify(xxx)),也有人说使用lodash/cloneDeep,更甚者自己封装一段递归方式去实现。相对来说JSON转换是最简单的,但是它不够完美,有一些场景是不能正常转换的。如下:
js
<script>
const obj1 = { a: 'a', b: 'b' } // 正常对象
const obj2 = { a: 'a', b: [1,2] } // 包含数组对象
const obj3 = { a: 'a', func: function(){} } // 函数对象
const obj4 = { a: 'a', o: { inner: 'inner' } } // 对象对象
const obj5 = { a: 'a', map: new Map() } // map 对象
const obj6 = { a: 'a', set: new Set() } // Set 对象
console.log(JSON.parse(JSON.stringify(obj1)));
console.log(JSON.parse(JSON.stringify(obj2)));
console.log(JSON.parse(JSON.stringify(obj3)));
console.log(JSON.parse(JSON.stringify(obj4)));
console.log(JSON.parse(JSON.stringify(obj5)));
console.log(JSON.parse(JSON.stringify(obj6)));
obj1.c = obj1 // 循环引用对象
console.log(JSON.parse(JSON.stringify(obj1)));
</script>
结果: 我们可以清晰的看到,当对象中包含这些场景时,JSON转换是不正确的
- 对象中存在
function
时,function
会无法转换 - 对象中存在
Map
|Set
时,会将Map
|Set
转换为普通对象 - 对象存在循环引用时,那么会直接报错导致程序终止 当然正常情况下,使用JSON转换是没太大问题的,也足以应对大多数情况。
优雅的克隆方式
它是一个构造函数 MessageChannel
,通过该构造函数,我们可以实例化一个消息通道,实例中包含了两个属性(port1, port2),表示两个端点,这两个端点之间可以相互传递消息。
js
<script>
const { port1, port2 } = new MessageChannel();
port1.postMessage('MessageChannel')
port2.onmessage = mes => {
console.log(mes);
}
</script>
可以看到我们传递过去的内容存在于 mes.data
属性中,现在我们只是传递字符串,那么我们可以传递对象过去。
js
const { port1, port2 } = new MessageChannel();
const obj1 = { a: 'a', b: 'b' } // 正常对象
const obj2 = { a: 'a', b: [1,2] } // 包含数组对象
const obj3 = { a: 'a', o: { inner: 'inner' } } // 对象对象
const obj4 = { a: 'a', map: new Map() } // map 对象
const obj5 = { a: 'a', set: new Set() } // Set 对象
port1.postMessage(obj1)
port1.postMessage(obj2)
port1.postMessage(obj3)
port1.postMessage(obj4)
port1.postMessage(obj5)
obj1.c = obj1 // 循环引用对象
port1.postMessage(obj1)
port2.onmessage = mes => {
console.log(mes.data);
console.log(obj1 === mes.data);
}
可以清楚地看到,传递过去的对象都能完美的克隆下来,包括 Map
| Set
循环引用等,但是注意函数对象是不能使用的,会报错。那么接下来我们就可以将其封装为一个函数。
js
const obj = { a: 'a', map: new Map() } // map 对象
function cloneDeep(obj){
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj)
port2.onmessage = mes => {
console.log(mes.data);
console.log(obj === mes.data);
}
}
cloneDeep(obj)
这时我们发现怎么把 mes.data
给它 return
出去呢,聪明的彦祖和仙女姐姐们应该想到了, 对!没错。就是Promise
。将其改为
js
const obj = { a: 'a', map: new Map() } // map 对象
function cloneDeep(obj){
return new Promise((resolve, reject) => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj)
port2.onmessage = mes => {
resolve(mes.data)
}
})
}
cloneDeep(obj).then(res => {
console.log('res', res);
})
结果如下
大功告成~~
写在最后
当然我们还可以再优化一下,例如做一下异常处理,避免其他人使用时传递了函数对象过来。 这不是最简单的对象克隆方式,但在面试中,如果考察到对象克隆,这无疑是最惊艳面试官的实现方式之一。此外,在日常开发中也能使用到该克隆方式。 再见啦~~