今天同事问我一个问题:”为什么我深拷贝数组没用啊,我改变其中的值后,原来的数组的值也改变了…”。作为一个努力学习的前端小白,我当然不能错过这个坑了,最后终于算是解决了,由此而引出这篇文章来记录这个坑。
引子
请看以下代码:
let arr1 = [1, 2, 3]; let arr2 = arr1; arr2[0] = 4; console.log(arr1);
老司机一看就知道控制台将打印[4, 2, 3]
,老司机当然也知道如何避免这个坑。用ES6来写的话很简单就避免了这个坑。
let arr2 = Array.from(arr1);
ES6就是这么简单。
抛出问题
我同事开发时写的代码大致如下:
let arr1 = [{ name: 'js' }]; let arr2 = Array.from(arr1); // 接下来他就要针对arr2进行操作了 arr2[0].name = 'css'; console.log(arr1);
我擦,上面的代码打印出的是{ name: "css" }
,Array.from
不管用了?我的同事满脸黑人问号。
Array.from
根据MDN,Array.from
主要用于从一个类似数组或可迭代对象中创建一个新的数组实例。MDN上Array.from
的polyfill中有一段代码:
while (k < len) { kValue = items[k]; if (mapFn) { A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); } else { A[k] = kValue; } k += 1; }
所以我推测Array.from
采用的是浅拷贝
,毕竟它的作用主要是用于转换类数组和可迭代对象。
Object.assign
let arr1 = [{ name: 'js' }]; let arr2 = []; arr1.map(obj => arr2.push(Object.assign({}, obj))); arr2[0].name = 'css'; console.log(arr1);
使用Object.assign
轻松解决了同事的问题。那么使用Object.assign
是不是就意味着万事大吉了呢?很可惜并不是,请看以下代码:
let arr1 = [{ status: 200, res: { info: 'succes' } }]; let arr2 = []; arr1.map(obj => arr2.push(Object.assign({}, obj))); arr2[0].res.info = 'failed'; console.log(arr1);
控制台打印的是info: failed
。没错,Object.assign
是浅拷贝。哈哈哈哈…
递归浅拷贝
有的同学,可能就说了,既然浅拷贝不行,那就递归浅拷贝实现深拷贝
喽。说干就干:
function clone(obj) { if (typeof obj !== 'object') { return obj; } let toStr = Object.prototype.toString; let newObj = {}; let isArray = toStr.call(obj) === '[object Array]'; if (isArray) { newObj = []; Object.keys(obj).map(key => newObj.push(clone(obj[key]))); } else { Object.keys(obj).map(key => newObj[key] = clone(obj[key])); } return newObj; }
测试以下:
let arr1 = [{ status: 200, res: { info: 'succes' } }]; let arr2 = clone(arr1); arr2[0].res.info = 'failed'; console.log(arr1);
没问题,通过。再测试以下:
let arr1 = [ [ { status: 200, res: { info: 'succes' } }, { status: 301, res: { info: 'test' } } ] ]; let arr2 = clone(arr1); arr2[0][0].res.info = 'failed'; console.log(arr1);
没问题,通过。
结束语
数组深浅拷贝的话题其实一直都存在,网上也有很多文章提出了很多解决方案,但大多本质都是递归,我写这篇文章也算是做出了自己的一些思考,并记录了一个坑点。