关于JavaScript的浅拷贝和深拷贝

袅袅青烟散 2019-06-29

前言

要理解 JavaScript中浅拷贝和深拷贝的区别,首先要明白JavaScript的数据类型。JavaScript有两种数据类型,基础数据类型和引用数据类型。
js的基本类型:undefined,null,string,boolean,number,symbol(es6新增),保存在栈内存中
js的引用类型:Object类型, Array类型,Date类型,RegExp类型,Function类型,基本包装对象(Boolean类型,Number类型,String类型),单体内置对象(Global对象,Math对象),保存在堆内存空间中

1.基本类型值和引用类型值的复制

1.1基本类型值

基本类型值是指在栈内存保存的简单数据段,在复制基本类型值的时候,会开辟出一个新的内存空间,将值复制到新的内存空间。

var a = 1;
var b = a;
a = 2;
console.log(a);//输出2;
console.log(b);//输出1;

var a = 1;

关于JavaScript的浅拷贝和深拷贝

var b = a;

关于JavaScript的浅拷贝和深拷贝

a = 2;

关于JavaScript的浅拷贝和深拷贝

从上面例子看出,当一个变量的值是基本类型,把它复制给另一个变量,复制完成后改变它的值,不会影响已经复制了它的值的变量。

1.2引用类型值
引用类型值是保存在堆内存中的对象,变量保存的只是指向该内存的地址,在复制引用类型值的时候,其实只复制了指向该内存的地址。

var a = {
   name: 'Kitty',
   age: '20',
   sex: 'man'
};
var b = a;
a.name = 'Jack';
console.log(a);//输出{name: 'Jack',age: 20,sex: 'man'}
console.log(b);//输出{name: 'Jack',age: 20,sex: 'man'}

var a = {name: ‘Kitty’,age: ‘20’,sex: ‘man’};

关于JavaScript的浅拷贝和深拷贝

var b = a;

关于JavaScript的浅拷贝和深拷贝

a.name = ‘Jack’;

关于JavaScript的浅拷贝和深拷贝

从上面例子看出,当一个变量的值是引用类型值,把它复制给另外一个变量,复制的只是指向储存对象内存的地址,所以复制完成后,改变它的值,会影响复制了它的值的变量。
注意:如果有两个变量的值是引用类型值,就算它们的值完全相同,它们也是不相等的,因为它们指向的内存地址不同,例子:

关于JavaScript的浅拷贝和深拷贝

2. 对象的浅拷贝与深拷贝

当一个变量是对象,如果像上面那样直接将一个变量赋值给另一个变量,如果改变某个变量的值,另外一个变量也会跟着改变,如果我们不想发生这种情况,就需要写一个函数用来拷贝对象。

深拷贝和浅拷贝的示意图大致如下:

关于JavaScript的浅拷贝和深拷贝

2.1 对象浅拷贝

var a = {name:'wanger'}
var b = Object.assign({}, a)
a===b // false
b.name = 'zhangsan'
a.name //'wanger'

上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆,这时候a与b指向的是不同的栈对象,所以对b.name重新复制也不会影响到a.name。但是如果a.name是一个对象的引用,而不是一个字符串,那么上面的代码也会遇到一些问题,参考如下代码:

var a = {name:{firstName:'wang',lastName:'er'}}
var b = Object.assign({}, a)
a===b // false
b.name.firstName = 'zhang'
a.name.firstName //'zhang'

b.name.firstName又影响到了a.name.firstName,这是因为Object.assign()方法只是浅层拷贝,a.name是一个栈对象的引用,赋值给b时,b.name也同样是这个栈对象的引用,很多时候,我们不想让这种事情发生,所以我们就需要用到对象的深拷贝。

2.2 对象深拷贝

2.2.1 万能的for循环实现对象的深拷贝

var obj = {
  name: 'FungLeo',
  sex: 'man',
  old: '18'
}
var obj2 = copyObj(obj)
function copyObj(obj) {
  let res = {}
  for (var key in obj) {
    res[key] = obj[key]
  }
  return res
}

2.2.2 JSON.parse(JSON.stringify(objectToClone))

var obj = {
  name: 'FungLeo',
  sex: 'man',
  old: '18'
}
var obj2 = JSON.parse(JSON.stringify(obj))

2.2.3 扩展运算符实现对象的深拷贝

var obj = {
  name: 'FungLeo',
  sex: 'man',
  old: '18'
}
var { ...obj2 } = obj
obj.old = '22'
console.log(obj)
console.log(obj2)

运行结果如下:
关于JavaScript的浅拷贝和深拷贝

3. 数组的浅拷贝与深拷贝

3.1 数组浅拷贝

var arr = ['old', 1, true, null, undefined];
var new_arr = arr.concat(); // 或者var new_arr = arr.slice()也是一样的效果;
new_arr[0] = 'new';
console.log(arr); // ["old", 1, true, null, undefined]
console.log(new_arr); // ["new", 1, true, null, undefined]

数组的浅拷贝,可用concat、slice返回一个新数组的特性来实现拷贝,但是如果数组嵌套了对象或者数组的话用concat、slice拷贝只要有修改会引起新旧数组都一起改变了,比如:

var arr = [{old: 'old'}, ['old']];
var new_arr = arr.concat();
arr[0].old = 'new';
new_arr[1][0] = 'new';
console.log(arr); // [{old: 'new'}, ['new']]
console.log(new_arr); // [{old: 'new'}, ['new']]

如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。这种叫浅拷贝 。
深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。

3.2 数组深拷贝

3.2.1 利用扩展运算符...对数组中嵌套对象进行深拷贝

var arr=[{a:1,b:2},{c:1,d:2}];
    var arr2=[];
    
    arr.forEach(item=>{
      var {...obj}=item;
      arr2.push(obj);
    })
    arr2[1].d=7
    
    console.log(arr,arr2)

关于JavaScript的浅拷贝和深拷贝

3.2.2 利用lodash库的cloneDeep方法

var arr=[{a:1,b:2},{c:1,d:2}];
var arr2=_.cloneDeep(arr)   

arr2[1].d=7;
console.log(arr,arr2)

关于JavaScript的浅拷贝和深拷贝

3.2.3 JSON.parse(JSON.stringify(objectToClone))

var arr=[{a:1,b:2},{c:1,d:2}];
var arr2=JSON.parse(JSON.stringify(arr));
    
arr2[1].d=7;
console.log(arr,arr2)

关于JavaScript的浅拷贝和深拷贝

相关推荐