JS浅谈深拷贝和浅拷贝

JS浅谈深拷贝和浅拷贝

在前端面试中经常会问到如何理解JS的深浅拷贝,又是如何实现的?
今天我们通过几个例子来理解一下。

浅拷贝

简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了。
下面我会引用到栈堆方面的内容来加强理解。
例:

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

这里写图片描述
修改了a,b也跟着变了。
接下来我们看一下原理:
引入基本数据类型与引用数据类型的概念了。
面试常问,基本数据类型有哪些,number,string,boolean,null,undefined五类。
引用数据类型(Object类)有Object,Array,Date等。
而这两类数据存储分别是这样的:
a.基本类型–名值存储在栈内存中,例如let a=1;
这里写图片描述
当你b=a复制时,栈内存会新开辟一个内存,例如这样:
这里写图片描述
所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

b.引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:
这里写图片描述
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
这里写图片描述
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
这里写图片描述
那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了
这里写图片描述

深拷贝

深拷贝,是拷贝对象各个层级的属性

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);

可以看到
这里写图片描述
跟之前想象的一样,现在b脱离了a的控制,不再受a影响了。

这里有个误区:
slice()和concat()并不是真正的深拷贝,原因是这两个拷贝不全,不彻底。
例子:

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

这里写图片描述
那是不是说slice方法也是深拷贝了,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有曾经的属性,还是这个例子,我们把a改改:

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

这里写图片描述
拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

这里引用知乎问答里面的一张图:
这里写图片描述
第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

如何实现真正的深拷贝?

1.JSON对象的parse和stringify

function deepClone(obj){
    let _obj = JSON.stringify(obj),
            objClone = JSON.parse(_obj);
    return objClone
}    
let a=[0,1,[2,3],4],
    b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

这里写图片描述
可以看到,这下b是完全不受a的影响了。

2.自己封装。

function deepCopy(obj){
 var newObj = obj.constructor === Array ? []:{};
 newObj.constructor = obj.constructor;
 if(typeof obj !== "object"){ 
 return ;
 } else if(window.JSON){
 //若需要考虑特殊的数据类型,如正则,函数等,需把这个else if去掉即可
 newObj = JSON.parse(JSON.stringify(obj));
 } else {
 for(var prop in obj){
  if(obj[prop].constructor === RegExp ||obj[prop].constructor === Date){
  newObj[prop] = obj[prop];
  } else if(typeof obj[prop] === 'object'){
  //递归
  newObj[prop] = deepCopy(obj[prop]);
  } else {
  newObj[prop] = obj[prop];
  }
 }
 } 
 return newObj;
}

我们需要把函数,正则等特殊数据类型也考虑在内,或者当前环境不支持JSON时,JSON方法也就不适用了。这时,我们可以通过递归来实现对象的深层复制。

3.JQ的extend方法。
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

这里写图片描述

借鉴:点击阅读原文

猜你喜欢

转载自blog.csdn.net/qq_36091581/article/details/79137527