【js进阶】你以为的深拷贝真的拷贝深吗

文章目录

      • 一、前言
      • 二、浅拷贝
      • 三、深拷贝
        • 1、JSON 对象中的 parse 和 stringify
        • 2、递归深拷贝
      • 四、首层浅拷贝

一、前言

  既然查询js拷贝相关的内容,那么就自作主张的认为屏幕前的你已经知道js的基本数据类型和引用数据类型,这里也就不再过多讲解。我们只需知道对于基本数据类型的拷贝实质是数据值的拷贝,并没有深浅拷贝的区别;浅拷贝和 深拷贝 针对的是JS中的引用类型,浅拷贝复制数据的引用地址,修改拷贝后的数据,原数据也会随之改变。只有深拷贝才是真正地对数据的拷贝,并且不会影响到原数据。
【js进阶】你以为的深拷贝真的拷贝深吗_第1张图片

二、浅拷贝

惯例,先看代码例子:

const originArr = ["test", 1, true];
const originObj = { a: 1, b: "test", c: true };

const cloneArr = originArr;
const cloneObj = originObj;

cloneArr.push("hello");
cloneArr[2] = false;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);

cloneObj["d"] = "world";
cloneObj["b"] = false;
console.log("cloneObj:", cloneObj);
console.log("originObj:", originObj);
console.log(cloneObj === originObj);

结果输出为:

cloneArr: [ 'test', 1, false, 'hello' ]
originArr: [ 'test', 1, false, 'hello' ]
true
cloneObj: { a: 1, b: false, c: true, d: 'world' }
originObj: { a: 1, b: false, c: true, d: 'world' }
true

总结出两点:

  • 1、ES6的const方式声明赋值的引用数据类型的常量的值(并非引用地址)其实是可以改变的;
  • 2、浅拷贝:引用数据类型通过=等号赋值方式拷贝,其实是对引用地址的拷贝,拷贝前后的数据指向同一个地址;
  • 3、浅拷贝的引用数据类型数据,数据属性值和元素的修改都是共享的,修改的是同一地址指向的堆内存空间值;
    【js进阶】你以为的深拷贝真的拷贝深吗_第2张图片

三、深拷贝

  深拷贝就是对目标的完全拷贝,不同于浅拷贝(引用地址的复制),深拷贝是对引用地址指向的堆内存数据的拷贝,并且在栈中创建新的引用地址指向拷贝后的堆数据。进行了深拷贝,新老数据老死不相往来,互不影响。
目前实现深拷贝的方法不多,主要是两种:

  • 利用 JSON 对象中的 parse 和 stringify
  • 利用递归来实现每一层都重新创建对象并赋值

1、JSON 对象中的 parse 和 stringify

const originArr = ["test", 1, true, { i: 1, j: "hhh" }];
const originObj = { a: 1, b: "test", c: true, e: { x: 1, y: "hhh" } };

const cloneArr = JSON.parse(JSON.stringify(originArr));
const cloneObj = JSON.parse(JSON.stringify(originObj));

cloneArr.push("hello");
cloneArr[2] = false;
cloneArr[3]["i"] = 2;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);

cloneObj["d"] = "world";
cloneObj["b"] = false;
cloneObj["e"]["x"] = 3;
console.log("cloneObj:", cloneObj);
console.log("originObj:", originObj);
console.log(cloneObj === originObj);

结果输出:

cloneArr: [ 'test', 1, false, { i: 2, j: 'hhh' }, 'hello' ]
originArr: [ 'test', 1, true, { i: 1, j: 'hhh' } ]
false
cloneObj: { a: 1, b: false, c: true, e: { x: 3, y: 'hhh' }, d: 'world' }
originObj: { a: 1, b: 'test', c: true, e: { x: 1, y: 'hhh' } }
false

  从结果中可以看到不管是数组和对象,通JSON的stringify和parse转换后,对拷贝的数组和对象的属性和元素进行修改和增添,无论是多少层的引用数据的改变,原数据都不受影响(记住这句话,后面有用)!

注意:JSON的parse()、stringify()对数据的转换也是有限制的,对于undified、function、symbol类型数据,在转换过程中会被忽略!而function在对象中是十分常见的!所以对于该方法,使用的时候也需要十分注意!

const fucObj = {
  f: function () {
    console.log(123);
  },
};

const cloneObj = JSON.parse(JSON.stringify(fucObj));
console.log("fucObj:", fucObj);
console.log("cloneObj:", cloneObj);

结果输出为:

fucObj: { f: [Function: f] }
cloneObj: {}

2、递归深拷贝

递归大家都懂吧,简单除暴,对每一层的数据都进行一次创建对象和对象赋值操作:

const fucObj = {
  f: function () {
    console.log(123);
  },
};

function deepClone(data) {
  // 判断拷贝的目标类型
  const cloneData = data.constructor === Array ? [] : {};
  // 遍历目标
  for (let keys in data) {
    //判断是否有遍历的属性值
    if (data.hasOwnProperty(keys)) {
      //如果是对象,或者是数组
      if (data[keys] && typeof data[keys] === "object") {
        // 进一步判断对象的属性值的类型
        cloneData[keys] = data[keys].constructor === Array ? [] : {};
        //如果是对象或者数组类型继续遍历
        cloneData[keys] = deepClone(data[keys]);
      } else {
        // 如果不是,就直接赋值
        cloneData[keys] = data[keys];
      }
    }
  }
  return cloneData;
}
console.log(deepClone(fucObj));

输出结果:

{ f: [Function: f] }

可以看出,通过遍历也可以实现数据的深拷贝!

四、首层浅拷贝

  对于首层浅拷贝,我们更愿意称之为首层深拷贝,因为其表达的意思是对引用数据类型进行深拷贝时,只有第一层引用进行了深拷贝,其余层引用仍是浅拷贝!这样的实现方式js中到处都是:

  • concat
  • slice
  • Object.assign()
  • ES6的解构赋值操作...
  • ···
    请看代码:
const originArr = ["test", 1, true, { i: 1, j: "hhh" }];

const cloneArr = originArr.concat();

cloneArr.push("hello");
cloneArr[2] = false;
cloneArr[3]["i"] = 2;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);

结果输出:

cloneArr: [ 'test', 1, false, { i: 2, j: 'hhh' }, 'hello' ]
originArr: [ 'test', 1, true, { i: 2, j: 'hhh' } ]
false

从结果我们不难看出,通过concat方法拷贝的数组cloneArr,修改cloneArr的第一层数据并不影响原数据originArr,但是修改cloneArr的第二层数据可以看到原数据originArr的数据也跟着一起改变了。通过下图可以做个简单的分析首层浅拷贝
【js进阶】你以为的深拷贝真的拷贝深吗_第3张图片
通过concat方法,对于多层嵌套的引用数据类型,第二层数据拷贝时不会在进行深拷贝,而是直接复制了同一个引用地址,所以最终还是指向同一个堆内存地址,所有修改都会会向影响!

欢迎大家关注本人的微信公众号,微信公众号将不定期发送相应学习文章和教程

微信号:chiyizao

或者微信公众号搜索:
迟亦早

你可能感兴趣的:(前端,javascript,js,深拷贝,浅拷贝,js进阶)