文章整理自我的个人博客文章:每日 Web 前端面试题(2021.05)
浅拷贝
浅拷贝后的对象的属性如果是引用类型,它仍旧指向源对象的同名属性。修改浅拷贝对象的引用类型下的属性,源对象也会被修改。实现方案有:
{...obj}
Object.assign({}, obj)
for...in
遍历对象获取 key,放到一个新的空对象里。
深拷贝
拷贝出来的对象,修改引用类型属性下的属性,源对象也不会变。二者真正意义上的分道扬镳。实现方案有 序列化并反序列化,以及 递归实现。
序列化并反序列化
JSON.parse(JSON.stringify())
。缺点是:
- 复制特殊的对象会出现问题,如 Date、RegExp
- 不能处理有环的对象,会报错
- 如果两个属性指向同一个对象,新对象的这两个同名属性指向各自的属性
手动实现
核心思路是:遍历属性,对于引用类型的属性,对其 递归,递归到所有属性都不是引用类型为止。
需要解决的问题:
- 循环引用,即对象下的某个属性指向这个对象。如果不进行特殊处理地递归,将会死循环。处理环的问题,通常是使用哈希表的方式解决:将遍历到的对象放到哈希表里(key 为源对象,value 为新对象),拷贝前检查哈希表是否有这个对象,有说明出现环,直接取对应的 value 为新对象即可。
- 相同引用,即多个属性指向同一个对象。循环引用是相同引用的特例。其实解决方案就是 循环引用 的处理方案。
- 特殊对象,普通的对象、数组这两个最基本的不必说,JS 内建的对象如 Date、RegExp 都要一个个做针对性的处理。如果还要考虑用户自定义的对象,也要针对性地处理,如补上原型链,针对性地实现 clone 方法等。
实现代码如下(对各种情况都进行了简单的单元测试):
/**
* 对象深拷贝
* 目前仅支持 普通对象 和 数组
* 处理了 循环引用 和 相同引用 的问题
*
* @param {any} obj 被拷贝对象
* @returns 新对象
*/
const cloneDeep = (obj) => {
const map = new Map() // oldObj -> newObj
function _cloneDeep(obj) {
if (obj === null || typeof obj !== 'object') return obj // 非对象值
if (map.has(obj)) { // 检测相同引用
return map.get(obj)
}
const retObj = Array.isArray(obj) ? [] : {} // (确定新对象的载体,是普通对象还是数组)
// 【易错点】这句代码必须放这里,不能放在 return 的前面,这是为了处理 “循环引用” 的情况
// 需要在发现循环对象时,这个循环对象已经在 map 中,否则会死循环。
map.set(obj, retObj)
for (const key in obj) { // 遍历属性
retObj[key] = _cloneDeep(obj[key])
}
return retObj
}
return _cloneDeep(obj)
}
复制代码
实际开发中并不需要过于完善的深拷贝,一般序列化反序列化就足够了。如果情况有些复杂,可以考虑使用 loadsh.cloneDeep
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END