99%的前端都不知道的lodash深拷贝的'BUG'

2,634 阅读2分钟

问题代码

import { cloneDeep } from 'lodash' 
const priceList = [1, 2] 
const animals = { priceList } 
const option = { series: ['dog', 'cat'].map(item => animals) } 
const o = cloneDeep(option) 
o.series[0].priceList[0] = 5
console.log('o: ', o) 

按道理来说这里的option里面的内容应该都已经被深拷贝了

但是我们来看打印结果

图片1.png

深拷贝失效了

在这里我们可以看到,事实上我们只改了series第一个数组里面 priceList里面的值,但是下面series[1].priceList[0]的值也发生了改变变成了5

显然lodash的深拷贝失效了

优先解决问题

因为这是业务当中实际遇到的问题,所以我必须优先把问题解决

第一种方案-暴力解决

使用 JSON.parse(JSON.stringify(data)) 就可以解决这个问题 但是使用这个的话,会存在var arr = [{name:'123',hd:undefined}] 数据转化后 undefined属性丢失的问题 不太稳妥

第二种方案-探索loadsh源码-寻求解决方案

因为内部源码太长,我将核心内容用伪代码的形式展现

const deepCopy = (data, map = new WeakMap()) => {
  if (typeof data !== 'object') {
    return data
  }
  if (map.get(data)) {
    return map.get(data)
  }
  const copy = new data.constructor()
  map.set(data, copy)
  for (let key in data) {
    if (data.hasOwnProperty(key)) {
      copy[key] = deepCopy(data[key], map)
    }
  }
  return copy
}

这段代码细看通过 map.get map.set 解决了对象循环引用的问题 但是却存在一个巨大隐患,对于这种const priceList = [1, 2] map.get会被认定为是同一个 直接return出去 返回了相同的引用地址 导致深拷贝失效

如何解决这个问题

const deepCopy = (data) => {
  if (typeof data !== 'object') {
    return data
  }
  const copy = new data.constructor()
  for (let key in data) {
    if (data.hasOwnProperty(key)) {
      copy[key] = deepCopy(data[key])
    }
  }
  return copy
}

直接进行深度递归赋值拷贝即可-这样即可解决重复引用的问题

基于实际业务的思考

虽然解决后的代码,没有能够解决对象循环引用的问题,但是注意一点。 我们做事情要基于实际业务。

比如虽然我们知道,循环引用的问题,但是你真的在实际业务当中有遇到过循环引用吗。

a.item = a 显然我还没有遇到过有人这么写代码产生循环引用

因此遇到这样的问题,我立马放弃了lodash的cloneDeep作为深拷贝工具 独立封装一个深拷贝函数供自己使用。

一切都要基于实际业务出发,而非为了技术而技术。