【前端面经】JS-深浅拷贝
理解深浅拷贝
深浅拷贝问题的出现是由于JavaScript对不同类型的存储方式而引发的。
对于原始数据类型,它们的值是直接存储在栈内存中;
而复杂数据类型,则在栈内存中记录它的指针,而指针指向堆内存中真正的值。
所以对于原始数据类型,就没有深浅拷贝一说;而对于复杂数据类型,浅拷贝就是仅复制指针,但被复制对象改变时,新复制的对象也会跟着改变;深拷贝则是连同堆内存中的数据完全拷贝一份,新旧对象的变化互不影响。
深浅拷贝的定义
浅拷贝:
拷贝复杂类型时,仅拷贝对象的指针;当原对象改变时,拷贝对象也会跟着改变。
深拷贝:
拷贝复制类型时,拷贝对象的值;当原对象改变时,拷贝对象不会跟着改变。
深浅拷贝的实现方案
浅拷贝方案:
- 展开运算符(ES6)
- Object.assign()
- concat
- …
深拷贝:
- 手写递归
- JSON.parse(JSON.stringfy())
- 先转换成字符串,再解析回对象
- 缺点是不能处理正则表达式和函数等特殊数据类型
- 一些库封装的深拷贝方法,例如Lodash的_.cloneDeep()
手写实现
// 先写一个浅拷贝
function _deepClone(target) {
let cloneRes = {};
for(const key in target) {
cloneRes[key] = target[key]
}
return cloneRes
// 函数出口
// if (typeof target !== 'obejct') return target
}
// 2. 普通深拷贝(只考虑对象/非对象)
function _deepClone(target) {
if (typeof target === 'object'){
// 如果是对象,进行处理
let cloneRes = {};
for(const key in target) {
cloneRes[key] = _deepClone(target[key])
}
return cloneRes
} else {
// 如果不是对象,直接返回
return target
}
}
// 3.考虑数组的深拷贝
function _deepClone(target) {
if (typeof target === 'object') {
let cloneRes = Array.isArray(target) ? [] : {}; // 核心
for (const key in target) {
cloneRes[key] = _deepClone(target[key])
}
return cloneRes
} else {
return target
}
}
// 4. 考虑循环引用
// Q:为什么要用WeakMap替代Map?
// A:强引用和弱引用的区别,强引用只能手动释放,弱引用能够被垃圾回收机制释放,使用Map会造成内存额外消耗。
// WeakMap中的键是弱引用,Map中的键是强引用
function _deepClone(target, map=new WeakMap()) {
if (typeof target === 'object') {
let cloneRes = Array.isArray(target) ? [] : {}; // 核心
// 防止循环调用导致的栈内存溢出
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneRes)
for (const key in target) {
cloneRes[key] = _deepClone(target[key], map)
}
return cloneRes
} else {
return target
}
}
// 5. 考虑其他数据类型
function _deepClone (target, map = new WeakMap()) {
if (target === null) return null
if (target instanceof Date) return new Date(target)
if (target instanceof RegExp) return new RegExp(target);
if (typeof target !== 'object') return target
if (map.get(target)) return map.get(target);
let cloneRes = new target.constructor()
map.set(target, cloneRes)
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneRes[key] = _deepClone(target[key], map);
}
}
return cloneRes
}
// 6. 性能优化
// 把for..in改为普通的for循环,性能会有所提高
// 测试案例
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
const newTarget = _deepClone(target)
newTarget.field4[1] = "Child2"
console.log(target);
console.log(newTarget);
console.log(target === newTarget);
// 输出:
// {
// field1: 1,
// field2: undefined,
// field3: { child: 'child' },
// field4: [ 2, 4, 8 ]
// }
// {
// field1: 1,
// field2: undefined,
// field3: { child: 'child' },
// field4: [ 2, 'Child2', 8 ]
// }
// false
参考链接&&相关文章
前端早读课【第2810期】JavaScript 深拷贝性能分析