作者:李旭光 引用请标明出处
深浅拷贝 1 2 3 4 5 6 let a = { age: 1 } let b = aa.age = 2 console .log(b.age)
从上述例子中我们可以发现,如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变。 通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个问题。
浅拷贝 首先可以通过 Object.assign
来解决这个问题。
1 2 3 4 5 6 7 8 let a = { age: 1 } let b = Object .assign({}, a)a.age = 2 console .log(b.age)
当然我们也可以通过展开运算符(…)来解决
1 2 3 4 5 6 let a = { age: 1 } let b = {...a}a.age = 2 console .log(b.age)
我们还可以用很多简单的方法都能实现浅拷贝:
1 2 arr.slice(); arr.concat();
通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝了
1 2 3 4 5 6 7 8 9 10 11 let a = { age: 1 , jobs: { first: 'FE' } } let b = {...a}a.jobs.first = 'native' console .log(b.jobs.first)
浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。
深拷贝 这个问题通常可以通过 JSON.parse(JSON.stringify(object))
来解决,这也是最好用最简单的方法,俗称乞丐版。乞丐版
1 2 3 4 5 6 7 8 9 10 11 let a = { age: 1 , jobs: { first: 'FE' } } let b = JSON .parse(JSON .stringify(a))a.jobs.first = 'native' console .log(b.jobs.first)
但是该方法也是有局限性的:
会忽略 undefined
会忽略 symbol
不能序列化函数
不能解决循环引用的对象
举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let obj = { a: 1 , b: { c: 2 , d: 3 , }, } obj.c = obj.b obj.e = obj.a obj.b.c = obj.c obj.b.d = obj.b obj.b.e = obj.b.c let newObj = JSON .parse(JSON .stringify(obj))console .log(newObj)
如果你有这么一个循环引用对象,你会发现你不能通过该方法深拷贝 在遇到函数、 undefined
或者 symbol
的时候,该对象也不能正常的序列化
1 2 3 4 5 6 7 8 9 10 let a = { age: undefined , sex: Symbol ('fmale' ), jobs: function ( ) {}, name: 'lixuguang' } let b = JSON .parse(JSON .stringify(a))console .log(b)
你会发现在上述情况中,该方法会忽略掉函数和 undefined
。 但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题,并且该函数是内置函数中处理深拷贝性能最快 的。
那么是否可以解决函数和循环引用的问题呢?答案是肯定可以解决,接下来是基础版本的改造基础版
1 2 3 4 5 6 7 8 9 10 11 function myClone (target ) { if (typeof target === 'object' ){ let cloneTarget = {}; for (const key in target){ cloneTarget[key] = myClone(target[key]) } return cloneTarget; } else { return target } }
写到这里已经可以帮助你应付一些面试官考察你的递归解决问题的能力。但是显然,这个深拷贝函数还是有一些问题。 这里只考虑了对象,没有考虑数组。 下面我们来做一个强化版的深拷贝,同时考虑对象、数组还有循环引用的问题。强化版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function myClone (target, map = new WeakMap( )) { if (typeof target === 'object' ){ let cloneTarget = Array .isArray(target) ? [] : {}; if (map.get(target)) { return target; } map.set(target, cloneTarget); for (const key in target) { cloneTarget[key] = myClone(target[key], map) } return cloneTarget; } else { return target; } }
当然如果你的数据中含有以上三种情况下,可以使用 lodash
的深拷贝函数。 如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function structuralClone (obj ) { return new Promise (resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } var obj = { a: 1 , b: { c: b } } (async () => { const clone = await structuralClone(obj) })()
深拷贝实现方式2,可以深拷贝 function
、symbol
,等等,堪称终极版终极版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 const mapTag = '[object Map]' ;const setTag = '[object Set]' ;const arrayTag = '[object Array]' ;const objectTag = '[object Object]' ;const argsTag = '[object Arguments]' ;const boolTag = '[object Boolean]' ;const dateTag = '[object Date]' ;const numberTag = '[object Number]' ;const stringTag = '[object String]' ;const symbolTag = '[object Symbol]' ;const errorTag = '[object Error]' ;const regexpTag = '[object RegExp]' ;const funcTag = '[object Function]' ;const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];function forEach (array, iteratee ) { let index = -1 ; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array; } function isObject (target ) { const type = typeof target; return target !== null && (type === 'object' || type === 'function' ); } function getType (target ) { return Object .prototype.toString.call(target); } function getInit (target ) { const Ctor = target.constructor; return new Ctor(); } function cloneSymbol (targe ) { return Object (Symbol .prototype.valueOf.call(targe)); } function cloneReg (targe ) { const reFlags = /\w*$/ ; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result; } function cloneFunction (func ) { const bodyReg = /(?<={)(.|\n)+(?=})/m ; const paramReg = /(?<=\().+(?=\)\s+{)/ ; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0 ].split(',' ); return new Function (...paramArr, body[0 ]); } else { return new Function (body[0 ]); } } else { return null ; } } else { return eval (funcString); } } function cloneOtherType (targe, type ) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default : return null ; } } function clone (target, map = new WeakMap( )) { if (!isObject(target)) { return target; } const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } if (map.get(target)) { return target; } map.set(target, cloneTarget); if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value)); }); return cloneTarget; } if (type === mapTag) { target.forEach((value, key ) => { cloneTarget.set(key, clone(value)); }); return cloneTarget; } const keys = type === arrayTag ? undefined : Object .keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; } clone(target);