js-最通俗的深浅拷贝

541 阅读6分钟

总有一些知识,你从来没听说过,但面试官却对它们情有独钟……

然而,你没听过的仅仅是它们的名字。

在开发中,你几乎天天都在用。

没错,

深浅拷贝就是它们中的一员。

image.png

what’s the 深浅拷贝?

啥叫拷贝?复制呗!考了那么多试,打了那么多小抄,拷贝啥意思岂不So Easy???

大家都知道,js的数据类型分为基本类型(Number、String、Boolean、null、undefined、Symbol)和引用类型(Object)两种。

内存中一共分为栈内存和堆内存两大区域:

堆和栈

栈(stack): 栈会自动分配内存空间,会自动释放。所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。

优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;

缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

堆(heap): 动态分配的内存,大小不定也不会自动释放。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。

基本类型的变量名和值都存在栈内存中,而引用类型的变量名存在栈内存中,变量值存在堆内存中,两者之间通过指针连接。

深浅拷贝面向who?

深浅拷贝讨论的是引用类型的赋值问题,不需要涉及基本类型。因为———— 基本类型的特点是每一次创建变量都会开辟一块内存来存值。所以把a复制给b,两个变量除了值一样,没有任何其他关系。

而引用类型的变量复制会有两种情况,一种是复制了指针(比如直接用等号复制:obj1=obj2),这样两个变量共同指向一个堆地址,这样就导致堆的值变化,两个变量的值就都发生了变化,也就是说,改变一个变量的值,就是改变了堆地址对应的值,从而第二个变量的值也就变了。另一种就是真正复制了一份值,两个变量指向不同的堆地址,但两个堆地址里面的值是相同的,这样的两个变量就不会相互影响,改变一个不会改变另一个。

有不少人认为第一种复制就是浅拷贝,第二种复制就是深拷贝,并且网络上的很多文章都是这样写的,我猜这样写的人一定没有经历过大厂的面试,否则面试官一定会提出质疑。

其实,js并没有官方地写过深浅拷贝到底是什么,但mdn上地诸多细节暴露了这两者的定义,如果有兴趣,可以看看mdn上关于Array.prototype.slice()的描述。

来个正经定义:

浅拷贝 是说:当你在拷贝两个引用类型数据时,如果被拷贝的数据中存在引用类型的属性值,也就是引用类型嵌套着引用类型,那么浅拷贝只能真正复制第一层,第二层只会复制指针。

浅拷贝只能真正拷贝一层,所以称为“浅”。

看例子:

// 定义一个有两层属性的对象:
var objj1={
  name:"xiaoming",
  hobby:{  
     best:"painting",
     second:"dancing"
  },
  dislike:{
     no1:"running",
     no2:"singing"
  }
};
// =号赋值:
var objj2=objj1;
// 浅拷贝:
var objj3=Object.assign({},objj1);
// 当objj1第一层属性变化时:
objj1.name="xiaolan";
objj1.dislike="running"
/* 输出objj2和objj3:
objj2:{
  name:"xiaolan",
  hobby:{  
     best:"painting",
     second:"dancing"
  },
  dislike:"running"
}
objj3:{
  name:"xiaoming",
  hobby:{  
     best:"painting",
     second:"dancing"
  },
  dislike:{
     no1:"running",
     no2:"singing"
  }
}
*/
// 可以看到,对于浅拷贝来说,当objj1的第一层属性变化时,objj3不会跟着变化。说明第一层时是真正的值的拷贝,不是只拷贝了指针。

//当objj1的第二层属性变化时:
objj1.hobby.best="singing";
// 输出objj3
/* objj3:{
  name:"xiaoming",
  hobby:{  
     best:"singing",
     second:"dancing"
  },
  dislike:{
     no1:"running",
     no2:"singing"
  }
} */
// 可以看到,当objj1的第二层属性变化时,浅拷贝了objj1的objj3的第二层属性也跟着变化了。说明,第二层只拷贝了指针,并不是真正值的拷贝。

深拷贝 则是在上述情况中,能够将嵌套的引用类型属性也真正的进行值的拷贝,并且适用于多层。

深拷贝能真正拷贝多层引用,所以称之为“深”。

看例子:

var objj1={
  name:"xiaoming",
  hobby:{  
     best:"painting",
     others:{
        no2:"dangcing",
        no3:"game"   
          }
  },
  dislike:{
     no1:"running",
     no2:"singing"
  }
};
// 深拷贝
var objj2=JSON.parse(JSON.stringify(objj1));
// 改变objj1的第三层属性
objj1.hobby.others.no2="travel";
// 输出objj2:
/*
objj2:{name: "xiaoming", hobby: {best: "painting",others: {no2: "dangcing", no3: "game"}},dislike: {no1: "running", no2: "singing"}}
可以看出,objj2并无变化。所以深拷贝对每一层都是真正的值的拷贝,不是只拷贝指针。
*/

看到这,相信深浅拷贝的定义对大家来说已经烂熟于心了。下面开始面试官的魔鬼问题第二弹——

hello~请手写深浅拷贝!

image.png

没关系,此时你要一定先淡定地说一句:好的。

  • 先看浅拷贝

js给数组、object内置了许多浅拷贝的方法,比如数组的concat、slice等方法;object的Object.assign()方法等。想多多了解的可以去mdn上搜一搜每一个的用法,但在面试和日常代码中,知道下面这个就足足足足足足够啦!

下面手写一种最常见的浅拷贝方法——Object.assign(),数组、object都可用(面试时直接写这个就好啦):

var o1={a:"a1",b:"b1",c:{c1:"1",c2:"2"}};
var arr1=[1,2,3,[4,5]];
var o1Coppy=Object.assign({},o1);
var arr1Coppy=Object.assign([],arr1);

怎么样!手写浅拷贝是不是很简单!!

  • 下面来说一说深拷贝

按理来说,既然可以浅拷贝,那多嵌套几个浅拷贝不就是深拷贝了吗?

所以深拷贝的第一个方法就是

  1. ——递归深拷贝!
//对象
var obj1 = {
    name: "cat",
    show:function(){
        console.log(this.name);
    }
};

//这种深拷贝函数不会丢失
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;
}
var obj3=deepClone(obj1);
//输出cat
obj3.show();

第二个方法就十分简单啦,一行代码就能解决!

  1. ——利用 JSON 对象中的 parse 和 stringify;不过,如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。
var obj1Coppy=JSON.parse(JSON.stringify(obj1));

注:JSON的parse 和 stringify是什么,百度一下就好啦~这里就不对赘述了。

······

至此,有没有一种自信油然而生————

行了,Stop!

就算是天王老子来问我,我也能给他讲到叫师父!

image.png