JavaScript 中浅拷贝与深拷贝的差异与实现方式整理

张开发
2026/4/14 16:14:20 15 分钟阅读

分享文章

JavaScript 中浅拷贝与深拷贝的差异与实现方式整理
文章目录JavaScript 中浅拷贝与深拷贝的差异与实现方式整理1.手写浅拷贝(shallowcopy)2.手写深拷贝(deep copy)JavaScript 中浅拷贝与深拷贝的差异与实现方式整理浅拷贝指的是创建一个新对象并把原对象第一层属性复制过去。对于基本类型属性会直接复制值对于引用类型属性只会复制引用地址不会继续递归拷贝内部对象。所以浅拷贝后• 外层对象不是同一个• 但里面嵌套对象可能还是同一个constobj1{name:Alice,age:25,info:{city:Beijing}}constobj2{name:Alice,age:25,info:obj1.info}深拷贝指的是创建一个全新的对象并且把原对象中每一层的数据都重新复制一份。对于嵌套对象或数组不再共用原来的引用地址因此修改新对象的任意层级都不会影响原对象。这样听起来可能比较抽象具体来说以lodash 这个套件提供的效用函式为例有分成clone和cloneDeep两种不同效用函式clone只用于浅拷贝(第一层拷贝)但cloneDeep可用于深拷贝。下面的例子说明两者的区别// lodash 的浅拷贝 clonevarobjects[{a:1},{b:2}];varshallow_.clone(objects);console.log(objectsshallow);// falseconsole.log(shallow[0]objects[0]);// true// lodash 的深拷贝 cloneDeepvarobjects[{a:1},{b:2}];vardeep_.cloneDeep(objects);console.log(objectsdeep);// falseconsole.log(deep[0]objects[0]);// false1.手写浅拷贝(shallowcopy)方法一手动复制值letobjA{a:1,b:{c:3},};letobjB{a:objA.a,b:objA.b};console.log(objAobjB);// falseconsole.log(objA.bobjB.b);// true, 第二层的对象是指向相同位置方法二使用展开参数letobjA{a:1,b:{c:3},};letobjB{...objA};console.log(objAobjB);// falseconsole.log(objA.aobjB.a);// true, 第二层的对象是指向相同位置console.log(objA.bobjB.b);// true, 第二层的对象是指向相同位置方法三使用Object.assignletobjA{a:1,b:{c:3},};letobjBObject.assign({},objA);console.log(objAobjB);// falseconsole.log(objA.bobjB.b);// true, 第二层的对象是指向相同位置2.手写深拷贝(deep copy)方法一使用JSON.parse(JSON.stringify(...))前置知识1.全局内建工具函数JSON 是 JS 全局内建工具对象类似于 Math2.序列化序列化serialization就是把内存里的复杂数据结构转换成一种 “可以存、可以传、可以写成字符串的形式”例如内存中的对象constobj{name:Tom,age:18,hobbies:[code,music]};序列化后{name:Tom,age:18,hobbies:[code,music]}3.有些数据类型不能被序列化会丢失数据类型结果function丢失undefined丢失Symbol丢失Date变字符串Map / Set变空对象循环引用直接报错这个做法是先将对象用JSON.stringify序列化为string再通过JSON.parse转换回对象。要特别注意这做法只能用于可序列化的物件有些无法序列化的物件例如function、HTML 的元素这些是无法序列化的所以执行前需要先确认是否可以序列化否则在执行JSON.stringify时会失败。letobjA{a:1,b:{c:3},};functiondeepCopy(item){returnJSON.parse(JSON.stringify(item));}letobjBdeepCopy(objA);console.log(objAobjB);// falseconsole.log(objA.bobjB.b);// false方法二使用structuredClone(value)针对可序列化的物件有另外一种透过JavaScript 内建的方法达成深拷贝。这种方法是structuredClone(value)用法如下。letobjA{a:1,b:{c:3},};letobjBstructuredClone(objA);console.log(objAobjB);// falseconsole.log(objA.bobjB.b);// false方法三手动递归式深拷贝初级版本适合面试functiondeepclone(obj){// 递归出口//如果里面还有对象,不能只复制最外层,要一直往里拷贝// 所以递归出口就是它不是对象或者它是空if(objnull||typeofobj!object)returnobj;//创建壳子,如果原数据是数组,就复制新的数组,如果原数据是对象,就要复制新的对象constcloneArray.isArray(obj)?[]:{};for(letkeyinobj){clone[key]deepclone(obj[key])}returnclone;}consttext{name:liming,info:{city:beijing,marks:[1,2,3,4,5]}}vardeepdeepclone(text);console.log(text.infodeep.info)拓展这个版本能处理普通对象和数组但不支持循环引用和特殊对象循环引用:constobj{}obj.selfobj这里的意思是• obj 是一个对象• 它有个属性 self• self 指向的不是别人还是它自己所以你可以理解成obj{self:obj}functiondeepClone(obj,mapnewWeakMap()){if(objnull||typeofobj!object){returnobj}if(map.has(obj)){returnmap.get(obj)}constcloneArray.isArray(obj)?[]:{}map.set(obj,clone)for(constkeyinobj){clone[key]deepClone(obj[key],map)}returnclone}建立一个映射.每次建完壳子都建立映射,到时候如果还是这个对象的时候,直接把 clone 给他返回去要不然就会无限的递归能够处理循环引用和特殊对象functiondeepClone(obj,mapnewWeakMap()){if(objnull||typeofobj!object){returnobj}if(objinstanceofDate){returnnewDate(obj)}if(objinstanceofRegExp){returnnewRegExp(obj)}if(map.has(obj)){returnmap.get(obj)}constcloneArray.isArray(obj)?[]:{}map.set(obj,clone)for(constkeyinobj){if(Object.prototype.hasOwnProperty.call(obj,key)){clone[key]deepClone(obj[key],map)}}returnclone}

更多文章