这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战;
前言
学习实现深拷贝之前必备的知识点和浅拷贝相同,没看过的小伙伴可以去这里查看:js的浅拷贝
好,我们继续看。
含义
深拷贝:对于复杂引用数据类型,它会在堆内存中完全开辟一块内存地址,并将原有的对象完全复制过来存放起来。这两个对象是相互独立,不受影响的。
- 总结就是:
- 将一个对象从内存中完整的拷贝出来一份给目标对象。
- 从堆内存中开辟一个全新的空间存放新对象,并且新对象的修改不会改变原对象。两者真正分离。
复制代码
方法
-
JSON.stringify
- 理解:就是把一个对象序列化为JSON的字符串,并将对象里面的内容转换成字符串,最后用JSON.parse()将JSON字符串生成一个新对象。
- 实例
let obj1 = { a:1, b:[1,2,3] } let str = JSON.stringify(obj1); let obj2 = JSON.parse(str); console.log(obj2); //{a:1,b:[1,2,3]} obj1.a = 2; obj1.b.push(4); console.log(obj1); //{a:2,b:[1,2,3,4]} console.log(obj2); //{a:1,b:[1,2,3]} 复制代码
- 注意点
-
拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
-
拷贝 Date 引用类型会变成字符串;
-
无法拷贝不可枚举的属性;
-
无法拷贝对象的原型链;
-
拷贝 RegExp 引用类型会变成空对象;
-
对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
-
无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。
-
- 例子
function Obj() { this.func = function () { alert(1) }; this.obj = {a:1}; this.arr = [1,2,3]; this.und = undefined; this.reg = /123/; this.date = new Date(0); this.NaN = NaN; this.infinity = Infinity; this.sym = Symbol(1); } let obj1 = new Obj(); console.log('obj=====',obj1) let str = JSON.stringify(obj1); let obj2 = JSON.parse(str); console.log('obj2====',obj2) 复制代码
-
递归实现(初级)
let obj1 = { a:{ b:1 } } function deepClone(obj) { let cloneObj = {}; for(let key in obj) { // 遍历传入的参数属性值 if(typeof obj[key] === 'object') { // 如果是对象,就再次调用该函数递归 cloneObj[key] = deepClone(obj[key]); }else { cloneObj[key] = obj[key]; // 基本类型的话直接赋值 } } return cloneObj; } let obj2 = deepClone(obj1); 复制代码
-
注意点
-
这个深拷贝同样也不能复制不可枚举属性和symbol类型;
-
这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
-
对象的属性里面成环,即循环引用没有解决。
-
-
-
递归实现(改进版)
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null); const deepClone = function(obj,hash = new WeakMap()) { if(obj.constructor === Date) { return new Date(); } if(obj.constructor === RegExp) { return new RegExp(); } if(hash.has(obj)) { return hash.get(obj); } let allDesc = Object.getOwnPropertyDescriptors(obj); let cloneObj = Object.create(Object.getPrototypeOf(obj),allDesc); hash.set(obj,cloneObj); for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]; } return cloneObj; } let obj = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '芒果啊'}, arr: [0, 1, 2], func: function () { console.log('芒果啊') }, date: new Date(0), reg: new RegExp('/A-Z/ig'), [Symbol('芒果')]: '啊', }; Object.defineProperty(obj,'innumerable',{ enumerable:false,value:'不可枚举属性'} ) obj = Object.create(obj,Object.getOwnPropertyDescriptors(obj)); obj.loop = obj; let cloneObj = deepClone(obj) cloneObj.arr.push(4) console.log('obj', obj) console.log('cloneObj', cloneObj); 复制代码
- 知识点:
-
能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 ** Reflect.ownKeys ** 方法;
-
参数为Date、RegExp类型,则直接生成一个新的实例返回;
-
利用 Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应特性。顺便结合Object的create方法创建一个新对象,并继承传入原对象的原型链。
-
利用 ** WeakMap ** 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏
-
- 知识点:
总结
其实在我们实际场景中,估计会使用现有的库来去实现深拷贝,比如lodash就很不错,有兴趣的小伙伴可以去了解一番。
前端漫漫长途,我们都在路上,希望可以和小伙伴们一起交流,一起进步。持续更新ing…..