理解js中的深拷贝和浅拷贝

·  阅读 284

1.前言

深拷贝和浅拷贝是经常在面试中会出现的,主要考察你对基本类型和引用类型的理解深度。

说到深浅拷贝,那在这就不得不提一下内存和数据类型了。

在JS当中,数据类型分为基本数据类型和引用类型,其中基本数据类型(string,number,boolean,undefined,null,symnol......),引用类型为Object(Array、Object、Function......)。内存分为栈内存和堆内存,其中栈内存用来存储基本数据类型(存取速度快,存放量小)和引用类型的地址(存取速度慢,存放量大,其引用指针存于栈区,并指向引用本身),而堆内存则存储引用数据类型。

2.赋值

赋值是将某一数值或对象赋给某个变量的过程

2.1 基本数据类型:赋值,赋值之后两个变量互不影响
2.2 引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响
<script>
    // 基本类型
    var a = 100;
    var b = a;
    a = 200;
    console.log(a, b); // 200, 100 ,a b指向不同的数据

    // 引用类型指向同一份数据
    var a = { c: 1000 };
    var b = a;
    a.c = 2000;
    console.log(a.c, b.c); // 2000, 2000 全是2000,a b指向同一份数据
</script>
复制代码

通常在开发中并不希望改变变量 a 之后会影响到变量 b,这时就需要用到浅拷贝和深拷贝。

3.浅拷贝

3.1 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

 <script>
  function cloneShallow(source) {
    var target = {};
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
    return target;
  }
  var a1 = { b: { c: {} } };
  var a2 = cloneShallow(a1); // 浅拷贝
  a2.b.c = { d: "1" };
  console.log("a1---", a1);
  console.log("a2---", a2);

  var a5 = { b: { d: [1,2] } };
  var a6 = cloneShallow(a5); // 浅拷贝
  a6.b.d = [3,4];
  console.log("a5---", a5);
  console.log("a6---", a6);

  // 注意:当object只有一层的时候,是深拷贝,例如如下:
  var a3 = { b:'9'};
  var a4 = cloneShallow(a3);
  a4.b = '10';
  console.log("a3---", a3);
  console.log("a4---", a4);
</script>
复制代码

WX20210615-160912@2x.png

3.2 Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。注意,Object.assgin() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

Object.assign(target, ...sources)

屏幕快照 2021-06-15 16.31.12.png

3.3 Array.prototype.concat()

屏幕快照 2021-06-15 16.37.23.png

3.4 Array.prototype.slice()

屏幕快照 2021-06-15 16.39.38.png

3.5 ...obj 展开运算符

展开运算符是 ES6 中新提出来的一种运算符。

屏幕快照 2021-06-15 17.04.06.png

4.深拷贝

4.1 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响

<script>
  function isObject(obj) {
    return typeof obj === "object" && obj != null;
  }
  function cloneDeep(source) {
    if (!isObject(source)) return source; // 非对象返回自身

    var target = Array.isArray(source) ? [] : {};
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        if (isObject(source[key])) {
          target[key] = cloneDeep(source[key]); // 注意这里
        } else {
          target[key] = source[key];
        }
      }
    }
    return target;
  }
  var obj = {
    title: "study",
    list: ["1", "2", "3"],
  };
  var obj2 = cloneDeep(obj);
  obj2.title = "play";
  obj2.list = ["3", "4"];
  console.log("obj", obj);
  console.log("obj2", obj2);
</script>
复制代码

WX20210615-161806@2x.png

4.2 JSON.parse(JSON.stringify(object))

JSON.stringify():将对象转成 JSON 字符串。
JSON.parse():将字符串解析成对象。
通过 JSON.parse(JSON.stringify()) 将 JavaScript 对象转序列化(转换成 JSON 字符串),再将其还原成 JavaScript 对象,一去一来我们就产生了一个新的对象,而且对象会开辟新的栈,从而实现深拷贝。
注意
注意,该方法的局限性:
1、不能存放函数或者 Undefined,否则会丢失函数或者 Undefined;
2、不要存放时间对象,否则会变成字符串形式;
3、不能存放 RegExp、Error 对象,否则会变成空对象;
4、不能存放 NaN、Infinity、-Infinity,否则会变成 null;
5、……更多请自行填坑,具体来说就是 JavaScript 和 JSON 存在差异,两者不兼容的就会出问题。

屏幕快照 2021-06-15 16.44.10.png

4.3 函数库 Lodash

 cloneDeep() 方法
复制代码
npm i --save lodash
var _ = require('lodash');

var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

复制代码

5.总结

通过学习整理了解到了深拷贝和浅拷贝的差异,在写业务代码的时候根据场景更好的拷贝数据,当然,文章也存在部分逻辑的不完整性,欢迎大家指正。

6 参考

木易杨前端进阶

分类:
前端