面试官:什么是深浅拷贝,手写一个深拷贝。

74 阅读3分钟

什么是深浅拷贝

  • 拷贝 顾名思义就是复制,把一个值赋值给另外一个值,那为什么还分深浅呢?
  • 首先我们得知道在js中,基本数据类型是保存在栈当中,而引用类型是保存在堆当中(指针在栈中)。

一般的原始类型赋值操作就是深拷贝,所以深拷贝和浅拷贝通常只针对引用类型来讨论。

浅拷贝

浅拷贝: 拷贝原对象,新对象会受原对象修改的影响

let obj1 = {
  name:'zxc',
  like:{
    a:'dance',
    b:'sing'
  }
}

let obj2 = obj1 
obj1.name= 'asd'
console.log(obj2);

image.png

这就是浅拷贝了,当修改obj1里面的值之后,obj2 里面的值也会改变

常见的浅拷贝方法也有许多: Object.assign(),Object.create(), {...obj}, slice(0), concat(),[...arr]等等

例如:

let obj1 = {
  name:'zxc',
  like:{
    a:'dance',
    b:'sing'
  }
}
const obj2 = Object.assign({},obj1)
obj1.name= 'asd'
obj1.like.a = rap
console.log(obj2);

image.png

这里使用了Object.asign来实现浅拷贝,可以看出obj1的name属性被修改了但是obj2的name并没有修改,然而obj1里面的like对象里面的a属性被修改了,所以这里还是浅拷贝,毕竟并不是所有的属性都不会被影响。浅拷贝比较简单,下面来聊聊深拷贝。

深拷贝

深拷贝: 拷贝原对象,新对象不会会受原对象修改的影响

深拷贝方法主要就是 JSON.parse(JSON.stringify())

var obj = {
    1 : 1,
    a :undefined,
    b :function(){},
    2 :[],
    c :{
        d : 2,
        e : 3
    },
   
}

let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj);

image.png

从打印结果可以看出: 少了一些东西, a :undefined,b :function(){}, 这两个东西好像没有了,这是为什么呢?

该方法是用JSON.parse将对象转为字符串,然后在用JSON.stringify转回对象json字符串转换为对象的时候,会自己去构建新的内存地址存放数据,但是不能拷贝undefined,Symbol,function,正则,NaN,Infinity,Date以及不能处理循环引用。

让我们来实现一个简陋的深拷贝

let obj = {
  name:'ggg',
  like:{
    a:'ctrl',
    b:'sing'
  }
}
let newObj = deepClone(obj)
function deepClone(obj) {
  if (obj === null) return null // 判断要拷贝的对象是不是null
  if (obj instanceof RegExp) return new RegExp(obj) // 判断要拷贝的是否为正则,是的话直接返回一个正则的实例对象
  if (obj instanceof Date) return new Date(obj) //判断要拷贝的是否为Date类型,是的话直接返回Date的实例对象
  let newObj = Array.isArray(obj) ? [] : {}//调用数组的API,来判断要拷贝的对象的类型是数组类型还是对象类型
  for (let key in object) { // 遍历这个obj对象,然后判断key对应的value是什么类型。
    if (Object.hasOwnProperty(key)) {//key是否是obj显示具有
      newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
      //如果是原始类型就直接赋值,如果得到的是一个对象那就递归的去判断 所得到的对象里面的 属性,直到判断到原始类型 赋值之后才会跳出来。
    }
    return newObj
 }
}

至此就实现了一个很简陋的深拷贝。

另一种深拷贝

let obj = {
  name: 'ggg',
  like: {
    a: 'ctrl',
    b: 'sing'
  }
}
//管道消息通信 
function deepClone(obj) {
  return new Promise((resolve, reject) => { //返回一个 Promise。该 Promise 在克隆的对象可用 resolve。
    const { port1, port2 } = new MessageChannel() //创建一个消息通道
    port1.postMessage(obj) //将对象发送到通道的第一个端口
    port2.onmessage = (msg) =>{ //在通道的第二个端口上注册一个处理程序,该处理程序将通过第一个端口的克隆对象作为消息接收。
      resolve(msg.data) // 可以在调用这个函数的时候接 .then()
    }
  })
}
deepClone(obj).then(res => {
  console.log(res);
})

image.png

这种方法也可以实现深拷贝。原理是:消息通道可以用于在两个线程之间传递数据。在这种情况下,消息通道用于在主线程和副线程之间传递数据。主线程将对象发送到消息通道的第一个端口,副线程在通道的第二个端口上接收对象,并将其克隆。

总结

以上就是一些对于深浅拷贝的理解,有其他的方法和修改建议,欢迎评论区提出和指正。