javaScript的深拷贝与浅拷贝

451 阅读5分钟

1、了解js的数据类型

js数据类型分为基本数据类型和引用数据类型

基本数据类型

number,string,boolean,undefined,null,symbol
变量的值存放在栈内存中,可直接修改变量的值,不存在深浅拷贝
例:

image.png

第一行代码:会为a这个标识符分配一个内存地址,这个内存地址存储数值100 image.png

第二行代码:把a的内容拷贝给了aCopy这个标识符,aCopy与a此时指向了同一个内存地址

image.png

第三行代码:给aCopy赋新的值,此时会重新开辟一个新的内存地址,并存储数值10000

image.png 第四行代码:发现改变aCopy的值并不会修改a的值,这是因为a是基本数据类型,基本数据类型是不能修改的,也就是不能在原来数字100的内存地址那里把存储的数字给修改了,要另外再开辟一个新的内存地址用来存储新的值

引用数据类型:

object,RegExp,math,Date对象
例:

image.png 第一行代码:为b这个标识符分配一个内存地址存放在栈中,这个内存地址对应的值又会被分配另一个地址,这个另一个地址会指向堆这样的数据结构,并且对应的属性也会被创建

image.png

第二行代码:把b的内容拷贝给了bCopy这个标识符,bCopy与b此时指向了同一个内存地址。bCopy与b这二个标识符从栈到堆这两种数据类型的链路都是一致的,如果bCopy要修改值时,就要沿路去寻找,找到了堆里面的属性值并进行修改 注意:这里是浅拷贝

image.png 第三行代码:将name:'Ali'修改为了'Tom',因此b的值也会发生变化

image.png

总结:基本数据类型与引用类型存储方式不一样,基本数据类型不存在深浅拷贝,只有引用类型存在深浅拷贝

2、深拷贝和浅拷贝

深拷贝:不会影响原对象的值,重新开辟一个新的内存地址
浅拷贝:会影响原对象的值,只是新增了一个变量栈内存的指针

浅拷贝的方法

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

let objA = { Ali: {age: 10},color:'blue' };
let objB = Object.assign({}, objA);
objB.Ali.age = 50;
objB.color = 'red'
console.log(objA,objB); 

image.png 可以看出对于对象中属性值是对象这种复杂类型的,Object.assign()无法实现深拷贝

2.函数库lodash的_.clone方法

该函数库也有提供_.clone用来做 Shallow Copy,后面我们会再介绍利用这个库实现深拷贝。

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
console.log(obj1.c === obj2.c);//false

3.展开运算符...

展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log(obj1,obj2)

image.png

4.Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr);

image.png

5.Array.prototype.slice()

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

深拷贝的实现方式

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

let arr = [1, 3, {
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr, arr4)
复制代码

这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

比如下面的例子:

let arr = [1, 3, {
    username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr, arr4)
复制代码

2.函数库lodash的_.cloneDeep方法

该函数库也有提供_.cloneDeep用来做 Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
复制代码

3.jQuery.extend()方法

jquery 有提供一個$.extend可以用来做 Deep Copy

$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
复制代码
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
复制代码

4.手写递归方法

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。关于这块如有疑惑,请仔细阅读ConardLi大佬如何写出一个惊艳面试官的深拷贝?这篇文章。

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);

参考链接:juejin.cn/post/684490…