js 深拷贝与浅拷贝

67 阅读4分钟

数据类型

Javascript中的数据类型分为两种,基本数据类型和引用数据类型。基本数据类型包括:Number,String,Undefined,Null,Boolean,Symbol,BigInt。引用数据类型包括:对象(Object),数组(Array),函数(Function)。
基本类型是按值访问的,不会影响到其他数据,如:

let a = '前端'
let b = a 
a = '前端工程师'
b //前端

所有基本数据类型的值没有深拷贝的概念,js中的浅拷贝和深拷贝,是针对引用数据类型(如Object,Array,Function)的复制问题。浅拷贝和深拷贝都可以实现在已有对象上再生出一份的作用。

深拷贝 VS 浅拷贝

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

image.png

浅拷贝: 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存
深拷贝: 会创造一个一摸一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

浅拷贝实现方式

1、Object.assign()

let obj = { a: {a: "hello", b: 21} };
let initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); //  "changed"

注意: 当object只有一层的时候,是深拷贝。例子如下:

  let obj1 = {a:10,b:20,c:30}
  let obj2 = Object.assign({},obj1)
  obj2.b = 100
  console.log(obj1) //{a:10,b:20,c:30}
  console.log(obj2) //{a:10,b:100,c:30}

2、Array.prototype.slice()

  let arr = [1,3,{username: 'jack'}]  
  let arr2 = arr.slice() 
  arr2[2].username = 'ross' 
  console.log(arr) // [1,3,{username: 'ross'}]

修改新对象会影响到原对象

3、Array.prototype.concat()

  let arr = [1,3,{username: 'jack'}]  
  let arr2 = arr.concat() 
  arr2[2].username = 'ross' 
  console.log(arr) //[1,3,{username: 'ross'}]

同样,修改新对象也会改到原对象。

关于数组的slice和concat方法的补充说明: 数组的slice和concat方法不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
原数组的元素会按照以下规则拷贝:

  • 如果该元素是个对象引用(不是实际的对象),slice或concat会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串、数字及布尔值来说(不是String、Number或者Boolean对象),slice会拷贝这些值到新的数组里,在别的数组里修改这些字符串或是数字或是布尔值,将不会影响另一个数组。

总结来说:

  • 当数组里的元素数据类型为字符串、数字及布尔,使用slice或concat方法再复制一份出来,修改新数组不会影响原数组,是深拷贝。
  • 当数组里的元素数据类型为引用类型,使用slice或concat方法再复制一份出来,修改新数组会影响原数组,是浅拷贝

示例如下:

let arr = [1, 3, {
      username: 'jack'
    }];
let arr2 = arr.slice();
arr2[1] = 2
console.log(arr); // [1,3,{username:'jack'}]
console.log(arr2); // [1,2,{username:'jack'}]

深拷贝实现方式

1、JSON.parse(JSON.stringify())

let arr = [1, 3, {
    username: 'ross'
}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'jack'; 
console.log(arr) //[1, 3, { username: 'ross'}];
console.log(arr2) //[1, 3, { username: 'jack'}];

原理: 用JSON.stringify()将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一来一回之间,新的对象产生了,而且新对象会开辟新的栈,实现深拷贝。

缺点: 此方法虽然可以实现数组或对象深拷贝,但是不能处理函数

2、手写递归方法

递归实现思路: 遍历对象、数组,对每一层的数据都实现一次创建对象、对象赋值的操作,就是深拷贝

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}

var obj = {
    name: 'Hanna',
    age: 22
}
var objCopy = deepClone(obj)
obj.name = 'ding';
console.log(obj);//Object {name: "ding", age: 22}
console.log(objCopy);//Object {name: "Hanna", age: 22}

参考文章

浅拷贝与深拷贝
深入JavaScript基础之深浅拷贝
什么是js深拷贝和浅拷贝及其实现方式
JavaScript浅拷贝和深拷贝