JavaScript系列 -- 深拷贝和浅拷贝

588 阅读4分钟

什么是深拷贝和浅拷贝?

深拷贝和浅拷贝是针对于 引用类型 (Array、Object)说的:

浅拷贝

浅拷贝就是指两个对象指向同一个内存地址,其中一个改变会影响另一个

就好比一栋大厦A,有别称B,所以大厦A和大厦B其实是指同一栋大厦,因为它们的地址是一模一样的。假设这栋大厦改建了,那肯定大厦A、大厦B都会改变呐

深拷贝

深拷贝就是指复制后的新对象重新指向一个新的内存地址,两个对象改变互不影响

就好比在大厦A之外再建一栋一模一样的大厦B,两栋大厦是不同地址,当然大厦A改建,大厦B就不会变化的啦

Object 浅拷贝的常用方法

1. 简单的赋值操作

var arr1 = [1,2,3];
var arr2 = arr1;
arr1[0] = 5;
console.log(arr1);    //  [5, 2, 3]
console.log(arr2);    //  [5, 2, 3]
console.log(arr1 === arr2);  //  true

2. 遍历原对象拷贝的方法(第一层深拷贝,第二层是对象/数组则是浅拷贝)

这里的 第二层 是指 object 里面的 object,当 obj2 拷贝 obj1 里面的 obj 时,会是浅拷贝

var obj1 = { a: {a: "hello"}, b: 20};
var obj2 = {};
var arr = Object.entries(obj1)
for(let i=0;i<arr.length;i++){
    Object.defineProperty(obj2,arr[i][0],{
        configurable: true,
        enumerable: true,
        writable: true,
        // 以上三个的默认值均为 false,所以得设置为 true
        value: arr[i][1]
    })
}
obj2.b = 100;
console.log(obj1);    // { a: 10, b: 20}
console.log(obj2);    //  { a: 10, b: 100};
console.log(obj1 === obj2);    //  false
console.log(obj1.a === obj2.a);    // true

3. Object.assign( { } , obj ) (第一层深拷贝,第二层是对象/数组则是浅拷贝)

var obj1 = { a: {a: "hello"}, b: 33 };
var obj2 = Object.assign({}, obj1);
obj2.a.a = "hello world";
console.log(obj1);    //  { a: {a: "hello world"}, b: 33 };
console.log(obj2);    //  { a: {a: "hello world"}, b: 33 };
console.log(obj1 === obj2);  //  false ,第一层是深拷贝
console.log(obj1.a === obj2.a);  //  true ,第二层如果还是对象则是浅拷贝

4. Object.fromEntires(Object.entires( obj ))(第一层深拷贝,第二层是对象/数组则是浅拷贝)

var obj1 = { a: {a: "hello"}, b: 33 };
var obj2 = Object.fromEntires(Object.entires( obj1 ));
obj2.a.a = "hello world";
console.log(obj1);    //  { a: {a: "hello world"}, b: 33 };
console.log(obj2);    //  { a: {a: "hello world"}, b: 33 };
console.log(obj1 === obj2);  //  false ,第一层是深拷贝
console.log(obj1.a === obj2.a);  //  true ,第二层如果还是对象则是浅拷贝

大多数的实际情况我们都是考虑使用深拷贝的,只是我们要注意如果我们在无意间使用了浅拷贝,就会有篡改其他对象/数组的事件发生。当然有时候我们也还是会用浅拷贝的,比如说公有的对象等等

Object 深拷贝的常用方法

1. obj2 = JSON.parse(JSON.stringify(obj1))

var obj1 = { a: {a: "hello"}, b: 33 };
var obj2 = JSON.parse(JSON.stringify(obj));
obj2.b = "hello world";
console.log(obj1);    //  { a: "hello", b: 33 };
console.log(obj2);    //  { a: "hello world", b: 33};
console.log(obj1 === obj2);  //  false

缺点:

  • 无法拷贝函数类型的属性
  • 会会抛弃对象的 constructor属性,也就是说无法追踪原来的构造函数是哪个

2. 递归拷贝

中间事先生成空数组[]或空对象{},地址就不一样了

function deepClone(obj1){
  if(typeof obj1 !== "object") return; // (因为有递归)进来先判断是否为引用类型
  let obj2 = obj1 instanceof Array ? [] : {}; // 判断是数组还是对象
  for(let key in obj1){
     if(obj.hasOwnProperty(key)){ // 拷贝原对象 obj 的自身属性就好
        obj2[key] = typeof obj1[key] === "object" ? deepClone(obj1[key]) : obj1[key];
    }      
  }  
  return obj2;  
}

测试:

let obj1 = {a: 11, b: function(){}, c: {d: 22}};
let obj2 = deepClone(obj1);  // {a: 11, b: f(), c: {d: 22}};
obj1 === obj2 // false

Array 浅拷贝

1. 直接赋值

var arr1 = [1,2,3]
var arr2 = arr1
arr2[0] = 4
console.log(arr1) // [4,2,3]
console.log(arr2) // [4,2,3]
console.log(arr1 === arr2) // true

2. arr2 = arr1.slice(0)(第一层深拷贝,第二层是对象/数组则是浅拷贝)

var arr1 = [1,2,{name: "Jack"}]
var arr2 = arr1.slice(0)
console.log(arr1 === arr2) // false
console.log(arr1[2] === arr2[2]) // true

3. arr2 = [].concat(arr1)(第一层深拷贝,第二层是对象/数组则是浅拷贝)

var arr1 = [1,2,[4,5,6]]
var arr2 = [].concat(arr1)
console.log(arr1 === arr2) // false
console.log(arr1[2] === arr2[2]) // true

4. arr2 = Array.from(arr1)(第一层深拷贝,第二层是对象/数组则是浅拷贝)

var arr1 = [1,2,[4,5,6]]
var arr2 = Array.from(arr1)
console.log(arr1 === arr2) // false
console.log(arr1[2] === arr2[2]) // true

Array 深拷贝

1. arrr2 = JSON.parse(JSON.stringify(arr1))

var arr1 = [1,2,[4,5,6]]
var arr2 = JSON.parse(JSON.stringify(arr1))
console.log(arr1 === arr2) // false
console.log(arr1[2] === arr2[2]) // false

2. 递归拷贝

deepClone方法

参考文章