如果說道實現深複製最簡單的方法,我們第一個想到的就是 JSON.stringify() 方法,因為JSON.stringify()後返回的是字串,所以我們會再使用JSON.parse()轉換為物件,如下程式碼:
let obj = { name: 'liaoyi',age: 22,sex: 1}
JSON.parse(JSON.stringify(obj))
但是這種克隆不夠完美,有一個致命的問題無法解決,就是她一旦遇到迴圈引用就會報錯:
let obj = { name: 'liaoyi',age: 22,sex: 1}
JSON.parse(JSON.stringify(obj))
obj.c = obj
console.log(JSON.stringify(obj))
js會報錯,無法把一個迴圈引用轉成 json 格式:
在這種情況下,我們通常想到的是寫一個正兒八經的深度克隆方法:
使用傳統方式實現物件的深複製
function deepClone(obj) {
const objectMap = new Map();
const _deepClone = value => {
const type = typeof value;
if (type !== 'object' || type === null) {
return value;
}
if (objectMap.has(value)) {
return objectMap.get(value);
}
const result = Array.isArray(value) ? [] : {};
objectMap.set(value, result);
for (const [key, _v] of Object.entries(value)) {
result[key] = _deepClone(value[key]);
console.log(key, _v);
}
return result;
};
return _deepClone(obj);
}
使用 MessageChannel 實現迴圈引用物件的深複製
不夠新鮮,我們來看一個好玩的 Web API
參考連結: MessageChannel
MessageChannel允許我們在不同的瀏覽上下文,比如window.open()開啟的視窗或者iframe等之間建立通訊管道,並透過兩端的埠(port1和port2)傳送訊息。MessageChannel以DOM Event的形式傳送訊息,所以它屬於非同步的宏任務。
// 透過這個建構函式,建立一個訊息通道,它會返回一個物件,解構 port1, port2 來實現通訊
const { port1, port2 } = new MessageChannel();
port1.postMessage('hello')
port2.onmessage = msg => {
console.log(msg.data) // hello
}
我們可以利用這個API,實現迴圈引用物件的深複製:
function deepClone(obj) {
return new Promise(resolve => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj);
port2.onmessage = msg => {
resolve(msg.data);
// console.log(obj, msg.data === obj); // false
};
})
}
const obj = { a: 1, b: '2' }
obj.c = obj;
deepClone(obj).then(res =>{
console.log('res',res);
})