JS-深浅拷贝

为了方便知识的复习和查看,将其记录下来,一块儿努力吧!前端

涉及的面试题:什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?面试

在说深浅拷贝以前,咱们须要了解一下不一样数据类型的变量的存储方式。数组

咱们都知道js的数据类型分为两大类:bash

  • 基本数据类型异步

    Number 、String、 Boolean、 Undefined、 Null、 Symbol函数

  • 引用数据类型post

    Object(Array/Rex)ui

基本数据类型保存在栈内存,引用类型保存在堆内存中。根本缘由在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,可是能够把它的地址写在栈内存中以供咱们访问。spa

let eg1 = {
    num:1,
    obj: {
        name:'gxm',
        age:18
    }
}
复制代码

这个eg1对象里的属性的储存状况以下:3d

浅拷贝

什么是浅拷贝?

浅拷贝只复制指向某个对象的指针,而不复制对象自己,新旧对象仍是共享同一块内存。因此原对象和拷贝后的对象是相互影响的。

如何实现浅拷贝?

(1)经过 Object.assign 来解决问题。

let real = {
    str: '我是本体',
    obj: {
        name:'gxm',
        age:18
    }
}
let clone = Object.assign({}, real)
clone.str = '我克隆了';
console.log(real.str);      //我是本体
console.log(clone.str);     //我克隆了
clone.obj.age = 16 ; 
console.log(real.obj.age);  //16
console.log(clone.obj.age); //16
复制代码

经过这个例子,咱们也能够看出Object.assign()只会拷贝全部属性值到新的对象中,若是属性值是对象的话,拷贝的是地址。因此Object.assign不是深拷贝。

(2)经过展开运算符(…)来解决

let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
复制代码

一般浅拷贝就能解决大部分问题了,可是当咱们遇到以下状况就须要使用到深拷贝了

let real = {
    str: '我是本体',
    obj: {
        name:'gxm',
        age:18
    }
}
let clone = {...real}
clone.str = '我克隆了';
console.log(real.str);      //我是本体
console.log(clone.str);     //我克隆了
clone.obj.age = 16 ; 
console.log(real.obj.age);  //16
console.log(clone.obj.age); //16
复制代码

浅拷贝只能解决了第一层的问题,若是接下去的值中还有对象的话,二者享有相同的引用。要解决这个问题,咱们须要引入深拷贝。

深拷贝

什么深拷贝?

深拷贝就是拷贝多层,嵌套的对象也会被拷贝出来,至关于开辟一个新的内存地址用于存放拷贝的对象。

个人理解深拷贝就是彻底拷贝,且原对象和拷贝后的对象没有任何关联。

如何实现深拷贝?

(1)使用 JSON.parse(JSON.stringify(object)) 深层拷贝

let realObj = {
    name: 'gxm',
    jobs: {
        first: 'BAT'
    }
}
let deepCloneObj = JSON.parse(JSON.stringify(realObj))
realObj.jobs.first = 'Google'
console.log(deepCloneObj.jobs.first) // BAT
复制代码

可是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
复制代码

若是你有这么一个循环引用对象,你会发现你不能经过该方法深拷贝。

img

(2)MessageChannel 深层拷贝

MessageChannel 深层拷贝能够用在含有有undefined和循环引用的场景。

// 有undefined + 循环引用
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
  f: undefined
}
obj.c = obj.b;
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c

function deepCopy(obj) {
  return new Promise((resolve) => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

deepCopy(obj).then((copy) => {           // 请记住`MessageChannel`是异步的这个前提!
    let copyObj = copy;
    console.log(copyObj, obj)
    console.log(copyObj == obj)
});
复制代码

运行结果以下:

但拷贝有函数对象时,仍是会报错。

(3)自实现深拷贝

eg1: 这个例子能够实现基本数据类型、引用对象类型和对象里嵌套对象的深层拷贝,但不适用于循环引用。所谓循环引用就是本身调用本身。

function deepClone(obj) {
    //obj是null的状况
    if(obj == undefined){
      return obj;
    }
    //obj是基本数据类型的状况
    if(typeof obj !== 'object'){//此处的object必定要小写
      return obj;
    }
    //正则、时间
    if(obj instanceof RegExp){
      return new RegExp(obj);
    }
    if(obj instanceof Date){
      return new Date(obj)
    }
    //若是obj是的数组或对象时
    let cloneObj = new obj.constructor;//得到obj的构造函数
    for(let key in obj ){
      if(obj.hasOwnProperty(key)){
        // cloneObj[key] = obj[key];
        //这种状况是obj[key]不是引用类型的值时,能够直接使用,可是若是obj[key]是多层嵌套的引用类型呢?就使用以下状况:
        cloneObj[key] = deepClone(obj[key]);
      }
    }
    return cloneObj;
}

let arrObj = {
    name:'gxm',
    age:18,
    friends:['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
    others:undefined,
    address:null
}
let newArrObj = deepClone(arrObj);
newArrObj.age = 20;
newArrObj.friends[4][0] = 'gg';
console.log('原对象------',arrObj);
console.log('新对象------',newArrObj);
复制代码

运行结果以下:

原对象------
{   name: "gxm",
    age: 18, 
    friends: ['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
    others: undefined,
    address: null
}
新对象------
{
    name: "gxm",
    age: 20, 
    friends: ['cc','yy','mm','nn',['gg','xx',['LL','QQ']]],
    others: undefined,
    address: null
}
复制代码

eg2: 若是拷贝的对象中的属性的值是循环引用的话,上面的深拷贝就不适用了,会崩掉的。要想解决这个问题,能够采用hash表进行映射。

function deepClone(obj,hash = new WeakMap()) {
//hash为了保存全部的数据,其实是使用WeakMap将数据暂存下来,能够防止内存泄漏

    //obj是null的状况
    if(obj == undefined){
      return obj;
    }
    //obj是基本数据类型的状况
    if(typeof obj !== 'object'){
      return obj;
    }
    //正则、时间
    if(obj instanceof RegExp){
      return new RegExp(obj);
    }
    if(obj instanceof Date){
      return new Date(obj)
    }
    
    /* 
    * 第一次拷贝的时候hash表里确定没有,下面的判断就不会执行。
    * 以后若是hash表里有这个对象,就return出来,下面的for循环就不会执行。
    * 即若是已经拷贝过了该属性,就不会再接着拷贝了,防止递归拷贝。
    */
    if(hash.get(obj)){
      return hash.get(obj);
    }
    
    //若是obj是的数组或对象时
    let cloneObj = new obj.constructor;//得到obj的构造函数
    
    //拷贝前和拷贝后进行对比
    hash.set(obj , cloneObj);
    
    for(let key in obj ){
      if(obj.hasOwnProperty(key)){
        cloneObj[key] = deepClone(obj[key], hash);
      }
    }
    return cloneObj;
}

let arrObj = {
    name:'gxm',
    age:18
}
arrObj.objj = arrObj;
let newArrObj = deepClone(arrObj);
console.log('原对象------',arrObj);
复制代码

运行结果以下:

参考掘金小册->前端面试之道

若是有误,请留言!

相关文章
相关标签/搜索