JS 实现深拷贝和浅拷贝
浅拷贝
浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
方式一
通过 Object.assign 来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
const a = { name: "javascript", age: 23 };
const b = Object.assign({}, a);
a.age = 86;
console.log("---a---", a); // ---a--- { name: 'javascript', age: 86 }
console.log("---b---", b); // ---b--- { name: 'javascript', age: 23 }
方式二
使用展开运算符…来实现浅拷贝
const a = { name: "javascript", age: 23 };
const b = { ...a };
a.age = 86;
console.log("---a---", a); // ---a--- { name: 'javascript', age: 86 }
console.log("---b---", b); // ---b--- { name: 'javascript', age: 23 }
但是上面两种方法在对象的属性值也为对象的时候就不适用了,这个时候就需要进行深拷贝。
const a = {
name: "javascript",
age: 23,
jobs: {
first: "student",
},
};
const b = { ...a };
a.jobs.first = "FE";
console.log("---b.jobs.first---", b.jobs.first); // FE
深拷贝
深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。
方式一
使用 JSON.parse(JSON.stringify())的方式来实现深拷贝。
const a = {
name: "javascript",
age: 23,
jobs: {
first: "student",
},
};
// const b = { ...a };
const b = JSON.parse(JSON.stringify(a));
a.jobs.first = "FE";
console.log("---a.jobs.first---", a.jobs.first); // FE
console.log("---b.jobs.first---", b.jobs.first); // student
该方法的局限性:
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
const a = {
name: undefined,
age: Symbol(23),
jobs: function () {},
hobby: "play game",
};
const b = JSON.parse(JSON.stringify(a));
console.log("---b---", b); // ---b--- { hobby: 'play game' }
方式二
如果所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel。
function structuralClone(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});
}
var obj = {
a: 1,
b: {
c: 2,
},
};
obj.b.d = obj.b;
// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
const clone = await structuralClone(obj);
console.log(clone);
};
test();
方式三
自定义实现一个深拷贝,但是其实实现一个深拷贝是很困难的,需要考虑多种边界情况,比如原型链如何处理、DOM 如何处理等等,所以只能实现一个简易版的深拷贝,此外还可以使用lodash 的深拷贝函数
// 不可以拷贝函数
function deepClone(obj) {
function isObject(obj) {
return (
(typeof obj === "object" || typeof obj === "function") && obj !== null
);
}
if (!isObject(obj)) return obj;
const isArray = Array.isArray(obj);
let newObj = isArray ? [...obj] : { ...obj };
// 用递归实现深拷贝
Reflect.ownKeys(newObj).forEach((key) => {
newObj[key] = isObject(newObj[key]) ? deepClone(newObj[key]) : newObj[key];
});
return newObj;
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3,
},
};
let newObj = deepClone(obj);
newObj.b.c = 1;
console.log(obj.b.c); // 2
深拷贝(尤雨溪版)
function find(list, f) {
return list.filter(f)[0];
}
function deepCopy(obj, cache = []) {
// just return if obj is immutable value
// 判断类型是否为原始类型,如果是,无需拷贝,直接返回
if (obj === null || typeof obj !== "object") {
return obj;
}
// if obj is hit, it is in circular structure
// 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
const hit = find(cache, (c) => c.original === obj);
if (hit) {
return hit.copy;
}
// 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
const copy = Array.isArray(obj) ? [] : {};
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy,
});
// 对引用类型递归拷贝直到属性为原始类型
Object.keys(obj).forEach((key) => (copy[key] = deepCopy(obj[key], cache)));
return copy;
}