# 深拷贝
# 介绍
我们知道在 javascript 中有八种数据类型:其中 number,string,undefined,null,boolean,symbol 和 bigint 为基本数据类型,而 object 为复杂数据类型
# 存储
- 对于基本数据类型,其值直接存储于栈中。
- 对于复杂数据类型,其值存储于堆中,而栈中只存储堆中的地址。这样做的好处有:
- 节省内存空间:存储在堆中的对象可以通过栈内的引用被访问和操作,意味着对象可以在不同的上下文中被共享和引用,从而节省内存。
- 垃圾回收:堆内存中的对象不再被引用时,垃圾回收机制就会自动进行回收,从而避免了内存泄漏和资源浪费。
- 动态分配内存:堆内存允许对象动态的增长和缩小,因此可以根据需要灵活地修改对象的结构和内容,使得 js 对象可以轻松地扩展以适应不同的应用需求。
# 深拷贝
而深拷贝和浅拷贝就是对于复杂数据类型 object 来说的,当拷贝了对象的一层(即堆的引用)时,就称之为浅拷贝,当拷贝了对象的两层(堆中创建新的对象)时,就称之为深拷贝。
# 实现方案
JSON.parse(JSON.stringify(obj)),第一种方法是使用 JSON 方法,但是这个方法有一些限制:JSON 序列化时函数会被转为 null,正则表达式会被转为空对象。递归实现:
// 是否是对象类型 | |
const isObject = (obj) => typeof obj === 'object' || typeof obj === 'function' && obj !== null; | |
function deepClone(target,map = new WeakMap()) { | |
if(map.get(target)) return target; | |
let constructor = target.constructor; // 获取到 constructor | |
// 如果是日期或者正则,就新创建一个实例 | |
if(/^(RegExp|Date)$/i.test(constructor.name)){ | |
return new constructor(target); | |
} | |
if(isObject(target)){ | |
map.set(target,true); | |
const cloneTarget = Array.isArray(target) ? []:{}; | |
for(let prop in target){ | |
if(target.hasOwnProperty(prop)){ | |
cloneTarget[prop] = deepClone(target[prop],map); | |
} | |
} | |
return cloneTarget; | |
}else { | |
return target; | |
} | |
} | |
// > 注意:1、原型链不能形成闭环(报错),2、__proto__的值只能是对象或者 null,3、一个对象只能又一个 [[Prorotype]],4、__proto__是内部 [[Prototype]] 的 getter/setter。 |
- MessageChannel 实现深克隆:由于浏览器不能将一个函数正确的复制到另一个线程中,所以不能支持函数的深克隆。
function deepCopy(obj) { | |
return new Promise((resolve) => { | |
const {port1, port2} = new MessageChannel(); | |
port2.onmessage = ev => resolve(ev.data); | |
port1.postMessage(obj); | |
}); | |
} | |
deepCopy(obj).then((copy) => {// 异步的 | |
let copyObj = copy; | |
console.log(copyObj, obj) | |
console.log(copyObj == obj) | |
}); |
- H5 新增 structuredClone
结构化克隆解决了该 JSON.stringify () 技术的许多(尽管不是全部)缺点。结构化克隆可以处理循环依赖,支持许多内置数据类型,并且更健壮且速度更快。
但是,它仍然有一些限制:
原型:如果你使用 structuredClone () 类实例,你将获得一个普通对象作为返回值,因为 structuredClone会丢弃对象的原型链 。
函数:如果你的 对象包含函数,它们将被悄悄丢弃 。
不可克隆: 有些值不是结构化可克隆的 ,尤其是 Error、 DOM 节点 和 Function。尝试这样做将引发 DataCloneError 异常。
属性描述符:setter 和 getter (以及类似元数据的功能) 不会被复制。例如,如果使用属性描述符将对象标记为只读,则复制后的对象中是可读写 (默认配置)。
RegExp: RegExp对象的lastIndex字段不会保留 。
# 浅拷贝实现方案
Object.assign({},obj),第二方法是使用 Object.assign ()const obj1 = {...obj2},使用扩展运算符