深拷贝 vs 浅拷贝
深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据。
// 基本类型var a = 1;var b = a;a = 2;console.log(a, b); // 2, 1 ,a b指向不同的数据// 引用类型指向同一份数据var a = {c: 1};var b = a;a.c = 2;console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据
对于引用类型,会导致a b指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个,有时候这可能不是我们想要的结果,如果对这种现象不清楚的话,还可能造成不必要的bug。那么如何切断a和b之间的关系呢,可以拷贝一份a的数据,根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝。
深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用
- 深复制在计算机中开辟了一块内存地址用于存放复制的对象,
- 浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
var a1 = {b: {c: {}};var a2 = shallowClone(a1); // 浅拷贝a2.b.c === a1.b.c // truevar a3 = clone(a1); // 深拷贝a3.b.c === a1.b.c // false
所谓的浅复制,只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”。Object.assign({}, obj1, obj2)
浅拷贝的实现
function shallowClone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target;}
深拷贝的实现
深拷贝的问题其实可以分解成两个问题,浅拷贝+递归,什么意思呢?假设我们有如下数据
var a1 = {b: {c: {d: 1}};
只需稍加改动上面浅拷贝的代码即可,注意区别
function clone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { if (typeof source[i] === 'object') { target[i] = clone(source[i]); // 注意这里 } else { target[i] = source[i]; } } } return target;}
其实上面的代码问题太多了,先来举几个例子吧
- 没有对参数做检验
function isObject(x) { return Object.prototype.toString.call(x) === '[object Object]';}function clone(source) { if (!isObject(source)) return source; // xxx}
- 判断是否对象的逻辑不够严谨
- 没有考虑数组的兼容
- 递归方法最大的问题在于爆栈,当数据的层次很深是就会栈溢出
方法: 用系统自带的JSON来做深拷贝的例子,下面来看下代码实现
function cloneJSON(source) { return JSON.parse(JSON.stringify(source));}
但是cloneJSON也有缺点,它的内部也是使用递归的方式
cloneJSON(createData(10000)); // Maximum call stack size exceeded
既然是用了递归,那循环引用呢?并没有因为死循环而导致栈溢出啊,原来是JSON.stringify内部做了循环引用的检测,正是我们上面提到破解循环引用的第一种方法:循环检测
var a = {};a.a = a;cloneJSON(a) // Uncaught TypeError: Converting circular structure to JSON
如何复制对象而不发生引用呢,对于数组,ES6我们复制有新的两种方法,不会发生引用。
第一种:Array.from(要复制的数组);var arr1=[1,2,3];var arr2=Array.from(arr1);arr1.push(4);alert(arr1); //1234alert(arr2); //123arr2.push(5);alert(arr1); //1234alert(arr2); //1235
第二种:...
var arr1=[1,2,3];var arr2=[...arr1];arr1.push(4);alert(arr1); //1234alert(arr2); //123arr2.push(5);alert(arr1); //1234alert(arr2); //1235
第二种这个方法也可以用在函数的行参上面。
function show(...arr1){ //直接来复制arguments这个伪数组,让它变成真正的数组,从而拥有数组的方法。 alert(arr1); //1234 arr1.push(5); alert(arr1); //12345}show(1,2,3,4)