js对象的浅克隆与深克隆
浅克隆
原始类型按值传递,对象类型按引用传递,与原对象共用一处内存,修改会使原对象也修改。
let obj = {
a: 100,
b: [10, 20, 30, 40],
c: {x: 10},
d: /^\d+$/
}
let obj2 = {};
for(let key in obj) {
// 遍历判断是否是obj的私有属性,是则返回true,
if(obj.hasOwnProperty(key)){
obj2[key] = obj[key];
}
}
//es6扩展运算符实现浅克隆
//let obj2 = {...obj};
console.log(obj === obj2); //false
obj2.c.x = 1000;
console.log(obj2.c.x); //1000
console.log(obj.c.x); //1000
深克隆
在内存中开辟一块新内存,将原对象中的所有值全部复制过去,与原对象完全脱离,修改新对象中的属性值不会影响原对象。
// JSON 方法实现 (对象的值为函数、日期、正则会受影响)
//let obj2 = JSON.parse(JSON.stringify(obj));
//console.log(obj2); //{ a: 100, b: [ 10, 20, 30, 40 ], c: { x: 10 }, d: {} }
// 递归函数实现
function deepClone(obj) {
// 过滤特殊情况
if(obj === null) return null;
if(typeof obj !== 'object') return obj;
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
// obj.constructor指向obj所属类, 克隆的结果和之前保持相同的类
let newObj = new obj.constructor;
for(let key in obj) {
if(obj.hasOwnProperty(key)){
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
let obj2 = deepClone(obj);
console.log(obj === obj2); //false
console.log(obj.c === obj2.c); //false
obj2.c.x = 100;
console.log(obj2.c.x); //100
console.log(obj.c.x); //10
缺陷
上面实现的深克隆存在缺陷,无法处理 Map、Set,以及循环引用等问题。
const object = {
set: new Set([10, 20, 90]),
map: new Map([['x', 10], ['y', 20]])
}
object.self = object // Maximum call stack size exceeded 循环引用
const object2 = deepClone(object)
console.log(object2) // 克隆结果map 和 set 为空, 无法处理 Map Set
改进
/**
* @description: 深克隆
* @param {*} obj
* @param {*} map 解决循环引用
* @return {*}
*/
function cloneDeep(obj, map = new WeakMap()) {
if (obj === null) return null;
if (typeof obj !== 'object') return obj;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
const objFromMap = map.get(obj)
if (objFromMap) return objFromMap
let target = {}
map.set(obj, target)
if (obj instanceof Map) {
// Map
target = new Map()
obj.forEach((k, v) => {
const key = cloneDeep(k, map)
const val = cloneDeep(v, map)
target.set(key, val)
})
} else if (obj instanceof Set) {
// Set
target = new Set()
obj.forEach((item) => {
target.add(cloneDeep(item, map))
})
} else if (obj instanceof Array) {
// Array
target = new Array()
obj.forEach((item) => {
target.set(cloneDeep(item), map)
})
} else {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = cloneDeep(obj[key], map);
}
}
}
return target
}
const object = {
set: new Set([10, 20, 90]),
map: new Map([['x', 10], ['y', 20]])
}
object.self = object
const object2 = cloneDeep(object)
console.log(object2)
其他实现深克隆的方式
MessageChannel
MessageChannel 是 HTML5 中的 Web Workers API 的一部分,用于在不同文档、不同窗口或者不同线程之间传递消息。而 postMessage 方法是用于发送消息的,它可以接受任何类型的参数,参数会自动进行结构化克隆算法,该算法能够处理循环引用并且可以拷贝大部分的 JS 对象。
function structuralClone(obj) {
return new Promise(resolve => {
const {port1, port2} = new MessageChannel();
port2.onmessage = event => resolve(event.data);
port1.postMessage(obj);
});
}
let obj = {a: 1, b: {c: 2}};
async function deepClone() {
const newObj = await structuralClone(obj)
return newObj
}
const obj2 = await deepClone(obj);
MessageChannel 的 postMessage 方法可以处理以下类型的特殊对象:
- 循环引用:可以处理对象之间的循环引用情况,如 let obj = {}; obj.a = obj;
- 内置类型:基本可以处理Javascript的所有内置类型,包括但不限于Array, Object, Map, Set, Date, RegExp等。
- TypedArray:用于处理二进制数据的类型,如Int8Array, Uint8Array, Float32Array等。
- Blob:用于表示不可变的原始数据的类.
- File:通常在处理用户上传文件时会用到。
- ImageData:用于表示canvas元素的数据。
但像函数这种特殊类型它并不能处理。
使用第三方库
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false