实现深拷贝的多种方式

来源 | http://www.fly63.com/article/detial/9650
浅拷贝主要拷贝的是对象的引用值,当改变对象的值,另一个对象的值也会发生变化。
1.简单深拷贝(一层浅拷贝)
①for循环拷贝
// 只复制第一层的浅拷贝function simpleCopy(obj1) {var obj2 = Array.isArray(obj1) ? [] : {};for (let i in obj1) {obj2[i] = obj1[i];}return obj2;}var obj1 = {a: 1,b: 2,c: {d: 3}}var obj2 = simpleCopy(obj1);obj2.a = 3;obj2.c.d = 4;alert(obj1.a); // 1alert(obj2.a); // 3alert(obj1.c.d); // 4alert(obj2.c.d); // 4
②Object.assign()实现一层深拷贝
var obj1 = {a: 1,b: 2,c: 3}var obj2 = Object.assign({}, obj1);obj2.b = 5;console.log(obj1.b); // 2console.log(obj2.b); // 5var obj1 = {a: 1,b: 2,c: ['a','b','c']}var obj2 = Object.assign({}, obj1);obj2.c[1] = 5;console.log(obj1.c); // ["a", 5, "c"]console.log(obj2.c); // ["a", 5, "c"]
③slice实现
// 对只有一级属性值的数组对象使用slicevar a = [1,2,3,4];var b = a.slice();b[0] = 2;alert(a); // 1,2,3,4alert(b); // 2,2,3,4// 对有多层属性的数组对象使用slicevar a = [1,[1,2],3,4];var b = a.slice();b[1][0] = 2;alert(a); // 1,2,2,3,4alert(b); // 1,2,2,3,4
④使用concat()方法
var a=[1,2,[3,4]]var c=[];var b=c.concat(a);b[0]=5;b[2][0]=6;console.log(b[0]);//5console.log(a[0])//1console.log(b[2][0]);//6console.log(a[2][0])//6
⑤es6的扩展运算符"..."
var a=[1,2,[3,4]]var b=[...a];b[0]=5;b[2][0]=6console.log(b[0]);//5console.log(a[0])//1console.log(b[2][0]);//6console.log(a[2][0])//6
⑥通过Object.create()实现
function deepCopy(obj) {var copy = Object.create(Object.getPrototypeOf(obj));var propNames = Object.getOwnPropertyNames(obj);propNames.forEach(function(name) {var desc = Object.getOwnPropertyDescriptor(obj, name);Object.defineProperty(copy, name, desc);});return copy;}var obj1 = { a: 1, b: {bc: 50, dc: 100, be: {bea: 1}} };var obj2 = deepCopy(obj1);obj2.a = 20;obj2.b.bc = 60;console.log(obj1.a)//1console.log(obj2.a)//20console.log(obj1.b.bc)//60console.log(obj2.b.bc)//60
2.粗暴深拷贝(抛弃对象的constructor)
使用jsON.stringify和jsON.parse实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象;
function deepCopy(obj1){let _obj = JSON.stringify(obj1);let obj2 = JSON.parse(_obj);return obj2;}var a = [1, [1, 2], 3, 4];var b = deepCopy(a);b[1][0] = 2;alert(a); // 1,1,2,3,4alert(b); // 2,2,2,3,4
缺陷:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON;
let obj1 = {fun:function(){alert(123);}}let obj2 = JSON.parse(JSON.stringify(obj1));console.log(typeof obj1.fun); // functionconsole.log(typeof obj2.fun); // undefined
3.复杂深拷贝(相对完美)
递归拷贝实现深拷贝,解决循环引用问题
/*** 判断是否是基本数据类型* @param value*/function isPrimitive(value){return (typeof value === 'string' ||typeof value === 'number' ||typeof value === 'symbol' ||typeof value === 'boolean')}/*** 判断是否是一个js对象* @param value*/function isObject(value){return Object.prototype.toString.call(value) === "[object Object]"}/*** 深拷贝一个值* @param value*/function cloneDeep(value){// 记录被拷贝的值,避免循环引用的出现let memo = {};function baseClone(value){let res;// 如果是基本数据类型,则直接返回if(isPrimitive(value)){return value;// 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值}else if(Array.isArray(value)){res = [...value];}else if(isObject(value)){res = {...value};}// 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝Reflect.ownKeys(res).forEach(key=>{if(typeof res[key] === "object" && res[key]!== null){//此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题if(memo[res[key]]){res[key] = memo[res[key]];}else{memo[res[key]] = res[key];res[key] = baseClone(res[key])}}})return res;}return baseClone(value)}
4.ES插件lodash
import lodash from 'lodash'var objects = [1,{ 'a': 1 }, { 'b': 2 }];var deep = lodash.cloneDeep(objects);deep[0] = 2;deep[1].a = 2;console.log(objects[0]);//1console.log(deep[0]);//2console.log(objects[1].a);//1console.log(objects[1].a);//2

评论
