【前端JS】深拷贝与浅拷贝的理解与实现

在这里插入图片描述

1. 前言

javaScript有深拷贝与浅拷贝之分,两者的区分在于由拷贝生成的新对象是否会随着源对象的改变而改变。
举例:对象B是通过对象A拷贝而来的,当对象A的某个属性发生改变时,若对象B相应的属性没有发生改变就是深拷贝;否则就是浅拷贝。

浅拷贝好理解,看下面代码:

function check(){
  var objA ={
    a:"foo",
    b:"bar"
  };
  var objB=objA;
  console.log(objA,objB);
  objA.a="FOO";
  console.log(objA,objB);
}

在这里插入图片描述
看到了吗?当objA.a修改了属性值之后,objB.a的属性也随着改变,这就是浅拷贝。

为什么objB对象的属性会随着objA的属性的改变而改变呢?理解数据存储方式就自然而然明白了。

2. 数据存储方式

数据的存储方式与数据的类型有关。
数据类型:

  1. 基本数据类型(number,string,boolean,null,undefined)
  2. 引用数据类型(Object类型:对象,数组,函数等)

基本数据类型存储方式:
基本数据类型的名值对存储在“栈”中。
举例:var str=“前端”;

在这里插入图片描述
当str2=str拷贝时,栈会开辟一个新的内存,看图:
在这里插入图片描述
由图片可以看出,基本数据类型在发生拷贝是会为变量的值也创建一个新内存,因此修改str的值是不会影响str2的值。
注意: 这并不是什么深拷贝,深浅拷贝是相对于对象而言的。这里只是说明基本数据类型的存储方式。

引用数据类型存储方式:
引用数据类型的键名是存储于栈中,而键名相对的值是存储于堆中。栈中会指定一个指针指向值所在的地址。
举例:上面声明的objA
在这里插入图片描述
当objB=objA拷贝时,实际上拷贝的是A地址,这时两对象同时指向同一地址。
在这里插入图片描述
则可以解释为什么当objA对象的a属性值改变时,为什么objB的a属性值也发生改变。因此两对象指向同一地址取值。
注意:这就给了我们启示,要想实现深拷贝,那让objB指向堆的一个新地址存储即可。

3. ES6提供了浅拷贝的方法(特别注意)

语法:Object.assign(...)
参数1:目标对象,即拷贝给谁
参数2:一个或多个源对象(被拷贝的对象)

该方法会遍历一个或多个源对象的所有可枚举的属性并把它拷贝(使用=操作符赋值)到目标对象,最后返回目标对象。

看下面代码:

function check(){
  var objA ={
    a:"foo",
    b:"bar"
  };
  var objB=Object.assign({},objA);
  console.log(objA,objB);
  objA.a="FOO";
  console.log(objA,objB);
}

在这里插入图片描述
看到运行结果肯定有人会问:上面不是说Object.assign()方法是浅拷贝吗?为什么ObjB的属性a没有随着ObjA的属性a改变?

解析: Object.assign()对于属性中只由基本类型组成的对象是实现深拷贝。上面的a,b都是基本类型的string字面量。那当对象中的某个属性也是一个对象呢?答案是浅拷贝。

将下面代码修改:对象中包含对象

function check(){
  var objA ={
    a:"foo",
    b:{c:"ccc",
       d:"ddd"}
  };
  var objB=Object.assign({},objA);
  console.log(objA,objB);
  objA.a="FOO";
  objA.b.c="CCC";
  console.log(objA,objB);
}

在这里插入图片描述
看到了吗?ObjB对象中的b对象的c属性值还会随着ObjA对象属性修改而变化。因此Object.assign()函数归根结底就是一个浅拷贝方法。
注意: 判断是深还是浅拷贝,需考虑全体属性是否随源对象变化而变化。

4.自己封装一个深拷贝函数

var obj={
  a:"foo",
  b:{c:"ccc",
     d:"ddd"}
}
function deepClone(obj){
  var cloneObj=Array.isArray(obj)?[]:{};//先判断obj是数组还是对象,确定clone的类型。不可以用typeof或instanceof,因为数组也是对象
 for(var key in obj){
   if(obj.hasOwnProperty(key)){//只拷贝obj对象中的属性,for...in遍历的是obj整条原型链的属性
    if(typeof(obj[key])==="object"&&obj[key]){//如果对象的属性还是对象,就要递归来拷贝属性中的对象,加入obj.key条件的原因是有时属性值为null,使用typeof也是返回object
        cloneObj[key]=deepClone(obj[key]);
    }else{
      cloneObj[key]=obj[key];
    }
   }
 }
 return cloneObj;
}
  var cloneObj=deepClone(obj);
  obj.a="FOO";
  obj.b.c="CCC";
  console.log(obj,cloneObj);

运行结果如图:
在这里插入图片描述
看到结果了吧,这就是深拷贝,就算属性是一个对象也不会再随源对象改变而改变。

5. 常用的深拷贝(使用JSON)

语法:var cloneObj=JSON.parse(JSON.stringify(obj));

看下面实现代码:

 var obj={
   a:"foo",
   b:{c:"ccc",
      d:"ddd"}
 }
 
  function deepClone(obj){
  var cloneObj=JSON.parse(JSON.stringify(obj));
  return cloneObj;
}

var cloneObj=deepClone(obj);
  obj.a="FOO";
  obj.b.c="CCC";
  console.log(obj,cloneObj);

在这里插入图片描述
结果是与自己封装的deepClone结果一样的。

注意: 该方法仅适用于对JSON安全的对象,因此只适合部分情况。所谓的JSON安全:当对象被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象,这就是JSON安全。
举例: 当对象属性是一个函数时,则JSON就不安全了。

var obj={
  a:"foo",
  b:function(){
    console.log("JSON");
  }
}
 function deepClone(obj){
  var cloneObj=JSON.parse(JSON.stringify(obj));
  return cloneObj;
}

var cloneObj=deepClone(obj);
  obj.a="FOO";
  console.log(obj,cloneObj);

看控制台:
在这里插入图片描述
看到了吧?拷贝时把函数类型的属性b给弄丢了。这就是JSON拷贝的缺点。

小结: 应该你们对深、浅拷贝都有了一个认识。其实实现深拷贝就是对属性是对象类型的利用递归拷贝,非对象数据类型就=操作符赋值即可实现。

猜你喜欢

转载自blog.csdn.net/weixin_43334673/article/details/106588172
今日推荐