likes
comments
collection
share

面试官:手写出考虑多种情况的【深拷贝】

作者站长头像
站长
· 阅读数 51

前言

最近在面试的时候,面试官要求手写一个深拷贝,要求考虑到多种情况。好吧,当时心凉凉,只会基本的深拷贝,于是面试完后赶紧来恶补下!

深拷贝与浅拷贝

在进入正题前,我们先来了解下深拷贝和浅拷贝是什么的概念吧。

1. 深拷贝: 创建一个新的对象来承接原对象中的原始值,且修改新对象不会影响原对象。

  • 实现方式: JSON.parse(JSON.stringify(xxx))

注:JSON.parse(JSON.stringify(xxx)) 不能拷贝undefined,symbol,函数,不能处理循环引用,正则,NAN,Infinity,Date

2. 浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 实现方式: Object.assign、解构赋值({...obj},[...obj])、数组身上的slice(),concat()等方法。

解构赋值:

  • 如果所解构的原对象是一维数组或对象,其本质就是对基本数据类型进行等号赋值,也就是深拷贝
  • 如果所解构的原对象是多维数组或对象,其本质就是对引用类型数据进行等号赋值,也就是浅拷贝

话不多说,下面进入正题!

基础版本

基本思路就是递归方法实现深度克隆。这种情况只考虑基本数据类型、对象和数组的情况。

//判断是否为引用类型
function isObject(obj){
  return typeof obj === 'object' && obj !==null
}

function deepClone(obj){
  //拷贝只针对是引用类型的情况
  if(!isObject(obj)) return 

  let newobj=Array.isArray(obj) ?[]:{}

  //遍历到所有类型
  for(let key in obj){
    if(obj.hasOwnProperty(key)){//只拷贝对象上显示具有的属性
      //obj[key]是原始类型才直接赋值,不是则递归再判断
      newobj[key]=isObject(obj[key]) ?deepClone(obj[key]):obj[key]
    }
  }
  return newobj
}

let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  }
}
let newObj=deepClone(obj);
console.log(newObj);

面试官:手写出考虑多种情况的【深拷贝】

还有另一种不常用到的方法可以实现!

function deepClone(obj) {
  return new Promise((resolve, reject) => {
    // 创建一个新的 MessageChannel 对象,并获取两个端口 port1 和 port2
    const { port1, port2 } = new MessageChannel(); 
    // 将要拷贝的对象发送出去
    port1.postMessage(obj); 
    // 监听 port2 收到的消息
    port2.onmessage = (msg) => { 
    // 将收到的消息的数据 resolve 出去
      resolve(msg.data); 
    }
  })
}

let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  }
}
deepClone(obj).then(res => {
  console.log(res);
})

这只是粗略的版本,这样虽然实现了深拷贝,但也存在一些问题:循环引用、Date、RegExp、Set、Map等类型不能正确深拷贝。

解决循环引用

主要是用WeakMap,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间WeakMap中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。当下一次垃圾回收机制执行时,这块内存就会被释放掉。

function isObject(obj){
  return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
  //拷贝只针对是引用类型的情况
  if(!isObject(obj)) return 

  let newobj=Array.isArray(obj) ?[]:{}

  //处理循环引用
  if(map.get(obj)){
    return map.get(obj)
  }
  map.set(obj,newobj)

  //遍历到所有类型
  for(let key in obj){
    if(obj.hasOwnProperty(key)){ //只拷贝对象上显示具有的属性
      //obj[key]是原始类型才直接赋值,不是则递归再判断
      newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
    }
  }
  return newobj
}


let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  },
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);

面试官:手写出考虑多种情况的【深拷贝】

处理其它数据类型

1) 拷贝Date,RegExp

function isObject(obj){
  return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){

  if(obj instanceof Date){
    return new Date(obj)
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  //拷贝只针对是引用类型的情况
  if(!isObject(obj)) return 
  let newobj=Array.isArray(obj) ?[]:{}

  //处理循环引用
  if(map.get(obj)){
    return map.get(obj)
  }
  map.set(obj,newobj)

  //遍历到所有类型
  for(let key in obj){
    if(obj.hasOwnProperty(key)){ 
      newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
    }
  }
  return newobj
}
let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  },
  date: new Date(),
  reg: /\d+/,
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);

面试官:手写出考虑多种情况的【深拷贝】

2) 拷贝Symbol

1. 由于for...offor...in 不能遍历到Symbol类型数据,所以采用这种方式时不会拷贝到Symbol类型属性。

2. 考虑Symbol类型的数据作为对象属性的情况。

解决办法:

  • Object.getOwnPropertySymbols():遍历Symbol类型的属性
  • Reflect.ownKeys():遍历所有数据类型的属性
  • Symbol.prototype.valueOf.call(obj)
function isObject(obj){
  return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){

  if(obj instanceof Date){
    return new Date(obj)
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  if(Object.prototype.toString.call(obj).slice(8,-1) ==='Symbol'){
    return Object(Symbol.prototype.valueOf.call(obj))
  }
  //拷贝只针对是引用类型的情况
  if(!isObject(obj)) return 
  let newobj=Array.isArray(obj) ?[]:{}

  //处理循环引用
  if(map.get(obj)){
    return map.get(obj)
  }
  map.set(obj,newobj)

  //遍历到所有类型
  Reflect.ownKeys(obj).forEach(key=>{
    if(obj.hasOwnProperty(key)){
      newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
    }
  })
  return newobj
}
let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  },
  date: new Date(),
  reg: /\d+/,
  [Symbol()]:'11',
  sym2:Object(Symbol(1)),
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);

面试官:手写出考虑多种情况的【深拷贝】

3) 拷贝Map,Set

主要是通过遍历这些Map,Set类型属性并逐个进行拷贝。

function isObject(obj){
  return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
  let newobj={}

  if(obj instanceof Date){
    return new Date(obj)
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  if(Object.prototype.toString.call(obj).slice(8,-1) ==='Symbol'){
    return Object(Symbol.prototype.valueOf.call(obj))
  }
  if(obj instanceof Set){
    newobj=new Set()
    obj.forEach(value=>{
       newobj.add(deepClone(value))
    })
    return newobj
  }
  if(obj instanceof Map){
    newobj=new Map()
    obj.forEach((value,key)=>{
       newobj.set(key,deepClone(value))
    })
    return newobj
  }

  //拷贝只针对是引用类型的情况
  if(!isObject(obj)) return obj
  newobj=Array.isArray(obj) ?[]:{}

  //处理循环引用
  if(map.get(obj)){
    return map.get(obj)
  }
  map.set(obj,newobj)

  //遍历到所有类型
  Reflect.ownKeys(obj).forEach(key=>{
    if(obj.hasOwnProperty(key)){
      newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
    }
  })
  return newobj
}
let obj={
  name:'方法',
  age:18,
  like:{
    type:'coding'
  },
  date: new Date(),
  reg: /\d+/,
  [Symbol()]:'11',
  sym2:Object(Symbol(1)),
  map: new Map([['t', 100], ['s', 200]]),
  set: new Set([1, 2, 3]),
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);

面试官:手写出考虑多种情况的【深拷贝】

实际上还有很多数据类型这里没有写到,有兴趣的话可以自行实现一下哈!

最后

本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!

转载自:https://juejin.cn/post/7267858684667756600
评论
请登录