浅谈深拷贝和浅拷贝

本文详细介绍了JavaScript中数据类型、存储方式以及浅拷贝与深拷贝的概念,重点讲解了Object.assign()、slice()、concat()等方法的浅拷贝效果,以及JSON.parse()、lodash、递归和jQuery.extend等深拷贝手段,并提到了structuredClone的使用条件。


在介绍浅拷贝和深拷贝之前我们先得需要知道js中的数据类型及其存储的方式

一、数据类型

js的数据类型分别有两大类基础数据类型和复杂(引用)数据类型:

  • 简单数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt
  • 杂数据类型(引用数据类型):Object

二、存储区别

基本数据类型:直接将数据储存在栈内存中。
引用数据类型:其地址存在栈内存中,而真实数据存储在堆内存中。
如下图所示:
在这里插入图片描述

三、深浅拷贝的定义

  • 浅拷贝:在栈内存中重新开辟一块内存空间,并将拷贝对象储存在栈内存中。
  • 深拷贝:基于浅拷贝的过程如果栈内存里面存储的是一个地址,那么其在堆内存空间中的数据也会被重新拷贝一个并由栈空间新创建的地址指向。

1.基础数据类型的拷贝

在对于基本类型的赋值操作都属于深拷贝,如下面的例子:

let a = 10;
let b = a;
a = 20;
console.log(a); // 20
console.log(b); // 10 

拷贝过程: 首先会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,这样两个变量其实指向的并不是同一个数据。

2.引用类型的拷贝

当引用类型数据进行赋值拷贝时都属于浅拷贝,如下面例子:

let a = [0, 1, 2, 3];
let b = a;
a[0] = 99
console.log(a); // [99,1,2,3]
console.log(b); // [99,1,2,3]

拷贝过程:在上面的例子中赋值的过程中只是将栈内存中指向地址的数据拷贝了一份,其实本质上两个地址都是指向同一份数据,当一方进行数据的修改,两方拿到的都是被修改的数据。

四、浅拷贝

1、Object.assign()

方法解释:方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象,可以实现一个浅拷贝的效果。当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
参数一:目标对象
参数二:源对象
示例:

methods: {
    dianji() {
      let obj1 = {
        a: 1,
        b: 2,
        c: ["c", "t", "r"],
      };
      let obj2 = Object.assign({}, obj1);
      obj2.c[0] = 1;
      obj2.a = 3;
      console.log(obj1, obj2);
    },
  },

在这里插入图片描述

注意:可见Object.assign()方法对于一维数据是深拷贝效果,但是对于多维数据是浅拷贝效果。

2、slice()

方法解释:数组进行截取,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组。
参数一:截取的起始位置
参数二:截取的结束位置
示例:

let a = [1,[1,2],3,4];
let b=a.slice();
a[0]=99;
b[1][0]=2;
console.log(a,b);

在这里插入图片描述
注意:可见slice()方法也只是对一维数据进行深拷贝,但是对于多维的数据还是浅拷贝效果。

3、concat()方法

方法解释:数组的拼接(将多个数组或元素拼接形成一个新的数组),不改变原数组,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组 (复制数组)。

let a = [1, 2, [3, 4]];
let c = [];
let b = c.concat(a);
a[0] = 99;
b[2][1] = 88;
console.log(a, b);

在这里插入图片描述

注意:可见concat()方法也只对一维数据具有深拷贝效果,对于多维的数据任然只是浅拷贝。

4、 ES6展开运算符

let a = [1,2,[3,4,5]]
let b=[...a]
a[2][0]=0
b[1]=99
console.log(a,b)

在这里插入图片描述

五、深拷贝

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

JSON.stringify() 将对象序列化成json对象;
JSON.parse() 反序列化——将json对象反序列化成js对象;

console.log(this.obj1, "修改前");
let obj2 = JSON.stringify(this.obj1);
let obj3 = JSON.parse(obj2);
obj3.a = "3";
console.log(obj3, this.obj1, "修改后");

注意:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object类型,这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,这种方法当对象的值为undefined、function、symbol 会在转换过程中被忽略,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

2、lodash

lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝

let _ = require("lodash");
let obj14 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
};
let obj24 = _.cloneDeep(obj14);
obj24.c[0]=8;
console.log(obj14,obj24)

在这里插入图片描述

3、递归拷贝

这也解决了笔试中的一个常见面试题那就是: 手写深拷贝
示例:

function deepClone(obj){
  let objClone =  Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === 'object') {
    for(let key in obj){
      if (obj[key] && typeof obj[key] === 'object'){ // 判断对象的这条属性是否为对象
        objClone[key] = deepClone(obj[key]); // 若是对象进行嵌套调用
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone; //返回深度克隆后的对象
}
let arrayA = [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}];
let arrayB = deepClone(arrayA);
arrayB[0]['aa'][0].ab = 2;
console.log(arrayA); // [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}]
console.log(arrayB); // [{a: 1, b: 2, aa: [{ab: 2, ac: [{ac: 1}]}]}]

//案例:
export const transListTotreeData = (listArr, pid) => {
// 定义一个空数组
  const treeArr = []
  // 遍历对象数组,根据pid找到所有的子级部门
  listArr.forEach(item => {
    if (item.pid === pid) {
      // 把找到的子部门放入数组
      treeArr.push(item)
      // 根据当前子部门的id去找所有的子部门
      const childrenArr = transListTotreeData(listArr, item.id)
      // 把找到的所有自己部门数组给到当前对象的children属性,
      // 当childrenArr为true,也就是大于0的时候
      if (childrenArr.length) { item.children = childrenArr }
    }
  })
  return treeArr
}

注意 : 以上面的方法为例使用递归的方式实现数组、对象的深拷贝,先判断各个字段类型,然后用递归解决嵌套数据。判断要进行拷贝的数据是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝,进行深拷贝的数据不能为空,并且必须是对象或者是数组。

4、通过jQuery的extend方法实现深拷贝

var array = [1,2,3,4];
var newArray = $.extend(true,[],array);

let $ = require('jquery');
let obj1 = {
   a: 1,
   b: {
     f: {
       g: 1
     }
   },
   c: [1, 2, 3]
};
let obj2 = $.extend(true, {}, obj1);

5、structuredClone

该方法为Web最新的 API,存在兼容问题

//注意:只有当对象中没有嵌套对象时,才可以实现深拷贝
const foo = {
    name: '张三',
    age: 24
}
const newFoo = Object.assign({}, foo)
foo.age = 25
console.log(foo, newFoo) // {name: '张三', age: 25} {name: '张三', age: 24}

// 对象中有内嵌的对象时
const foo = {
    name: '张三',
    info: {
        age: 24
    }
}
const newFoo = Object.assign({}, foo)
foo.info.age = 25
console.log(foo, newFoo) 
// { name: '张三', info: { age: 25 } } { name: '张三', info: { age: 25 } }

const foo = {
    name: '张三',
    info: {
        age: 24
    }
}
const newFoo = structuredClone(foo) // 
foo.info.age = 25
console.log(foo, newFoo) // { name: '张三', info: { age: 25 } } { name: '张三', info: { age: 24 } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值