区别
引用类型的值拷贝还是引用拷贝的区别,也就是当 b 拷贝 a, 如果修改 b, a 也改变,则为浅拷贝,如果修改 b, a 不发生改变,则为深拷贝。
深拷贝实现
1. 只拷贝对象和数组的递归实现
function isObject(obj) { return (typeof obj === 'object') && obj !== null; }function deepCopy(obj) { if (!isObject(obj)) { throw new Error('object 不是一个对象'); } let isArray = Array.isArray(obj); let cloneObj = isArray ? [] : {}; for(let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepCopy(obj[key]) : obj[key] } return cloneObj;}能拷贝undefined, 但是对于 function、Date、RegExp 和Error无法复制,因为它们有特殊的构造函数。
场景:
1. 对象 key 值为 Symbol 时不能拷贝,因为 for in 无法获取 Symbol 类型的键,可以通过Object.getOwnPropertySymbols() 来获取 Symbol 类型的键。
支持拷贝 Symbol 类型的键:
function isObject(obj) { return (typeof obj === 'object') && obj !== null; }function deepCopy(obj) { if (!isObject(obj)) { throw new Error('object 不是一个对象'); } const isArray = Array.isArray(obj); const cloneObj = isArray ? [] : {}; const symbolKeys = Object.getOwnPropertySymbols(obj) || []; symbolKeys.forEach((symbolKey) => { cloneObj[symbolKey] = isObject(obj[symbolKey]) ? deepCopy(obj[symbolKey]) : obj[symbolKey]; }) for(let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepCopy(obj[key]) : obj[key]; } return cloneObj;}2. 对象成环,obj1 添加一个属性,指向自身
const obj1 = { a: 'test'}; obj1.g = obj1;通过递归实现会出现栈溢出
通过序列化和反序列化也会报错:
如何处理对象成环
lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到,从而直接返回结果,悬崖勒马。这种算法思想来源于 HTML5 规范定义的结构化克隆算法,它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。
当然,设置一个哈希表存储已拷贝过的对象同样可以达到同样的目的:
function isObject(obj) { return (typeof obj === 'object') && obj !== null; }function deepCopy(obj, hash = new WeakMap()) { if (!isObject(obj)) { throw new Error('object 不是一个对象'); } // 查表 if (hash.has(obj)) return hash.get(obj); const isArray = Array.isArray(obj); const cloneObj = isArray ? [] : {}; const symbolKeys = Object.getOwnPropertySymbols(obj) || []; // 哈希表设值 hash.set(obj, cloneObj); symbolKeys.forEach((symbolKey) => { cloneObj[symbolKey] = isObject(obj[symbolKey]) ? deepCopy(obj[symbolKey], hash) : obj[symbolKey]; }) for(let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepCopy(obj[key], hash) : obj[key]; } return cloneObj;}2. 序列化和反序列化
const cloneObj = JSON.parse(JSON.stringify(obj1))不能拷贝 undefined,对象类型只能深拷贝对象和数组,对于其他种类的对象,会失真。