手写实现深拷贝,参考Lodash源码

187 阅读2分钟

一、采用JSON字符串实现深拷贝

let obj = {
  name:'zhangsan',
  age:13,
  hobby:['basketball','pingpang','dance'],
  score:{
    a:1,
    b:2
  }
}
// 使用JSON进行深拷贝
let res = JSON.parse( JSON.stringify(obj))
// 测试代码
res.score.a = 12
console.log(res,obj)

缺点:

1. 对象方法不会拷贝

2. 数组中的undefined会变为null,对象上的undefined不会拷贝

3. symbol不会拷贝

4. 正则表达式变成了空对象

5. Infinity会变为null

6. 一些类型的对象会被转换为字符串,比如:Date对象

7. 循环引用会报错

obj.score = obj
// 使用JSON进行深拷贝
let res = JSON.parse( JSON.stringify(obj))

8. 不能转换BigInt类型,会报错

二、递归实现

难点:

  • 解决JSON字符串深拷贝的缺点,解决方式详见代码
  • 循环引用问题
    • 使用:数组或set保存已经处理过的数据,解决循环引用的问题(本案例使用数组)
// 封装一个获取数据类型的函数
function toType(data) {
  return Object.prototype.toString.call(data).slice(8, -1)
}

// 使用递归方式处理深拷贝 ,参数1:要拷贝的数据,参数2:解决循环引用(初始不用传参)
const deepClone = function (target, done = []) {
  // 处理一些特殊数据类型
  // 如果是null或undefined,如果是就返回该值
  if (target == null) return target  
  // 获取数据的类型,以及其构造器(避免后面频繁用到)
  let type = toType(target)
  let col = target.constructor  // 当前
  // 如果是正则或date返回新的实例
  if (type === "RegExp" || type === "date") return new col(target)    
  // 如果是ERROR实例,返回该信息的实例
  if (type === "Error") return new col(target.message)
  // 如果是函数,返回一个新的函数包裹该函数
  if (type === 'Function') return function () {                         
    target.apply(this, arguments)
  }
  // 不是对象和数组(除以上的其他基本数据类型),则返回该数据
  if (type !== "Array" && type !== "Object") return target               
  // 解决循环引用问题
  // 如果已经处理过该数据,就直接返回该数据(不处理)
  if (done.includes(target)) return target   
  // 没有处理过的数据就放入该数组中,在递归时将done做为第二个参数传入
  done.push(target)                                                      
   // 如果是数组或者对象,遍历递归
  // 创建一个新的数组或对象,用于存储拷贝后的数据
  let res = new col()
  // 遍历对象或者数组
  Object.keys(target).forEach(key => {  
    //递归,第二个参数处理循环引用问题
    res[key] = deepClone(target[key], done)                               
  })
  return res
}