欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS: 深拷贝

程序员文章站 2022-05-08 07:54:17
注意: 以下深拷贝仅针对对象。 对于深拷贝,我平时用得很少,一般都是用 JSON 的方法来实现: 但前几天踩了坑,在网上查了才发现问题,只能说坑只有踩过才知道深浅。 坑 1. 对于 function、undefined,会丢失这些属性。 2. 对于 RegExp、Error 对象,只会得到空对象 3 ......

注意:以下深拷贝仅针对对象。

对于深拷贝,我平时用得很少,一般都是用 json 的方法来实现:

let newobj = json.parse(json.stringify(oldobj))

但前几天踩了坑,在网上查了才发现问题,只能说坑只有踩过才知道深浅。


  1. 对于 function、undefined,会丢失这些属性。

  2. 对于 regexp、error 对象,只会得到空对象

  3. 对于 date 对象,得到的结果是 string,而不是 date 对象

  4. 对于 nan、infinity、-infinity,会变成 null

    let oldobj = {
      test1: null,
      test2: undefined,
      fn: () => {console.log('fn')},
      date: new date(),
      regexp: /(a|b)/g,
      error: new error('err'),
      nan: number('nan')
    }
    
    let newobj = json.parse(json.stringify(oldobj))
    
    // 丢失 function、undefined
    // error、regexp 为空对象
    // nan 变为 null
    // date 对象变为 string
    newobj
    /*
    {
     error: {}
     regexp: {}
     date: "2019-04-16t11:43:05.870z"
     nan: null
     test1: null
    }
    */
  5. 无法处理循环引用

    let oldobj = { }
    oldobj.obj = oldobj
    
    // 会报错
    let newobj = json.parse(json.stringify(oldobj))
    // typeerror: converting circular structure to json

浅拷贝


浅拷贝方法还是挺多的,列举一二:

  1. object.assign()

    let oldobj = {
      name: 'parent',
      children: 'children'
    }
    
    let newobj = object.assign({}, oldobj)
  2. 循环

    /*
     * 浅拷贝,仅针对对象
     * params {object} obj
     */
    function shallowcopy (obj) {
      if (object.prototype.tostring.call(obj) !== '[object object]') {
        throw new typeerror(`${obj} is not a object`)
      }
    
      let res = {}
      for (let key in obj) {
        if (obj.hasownproperty(key)) {
             res[key] = obj[key]
        }
      }
    
      return res
    }

深拷贝


深拷贝的实现也是可以使用for...in + 递归实现的:

function isobject (obj) {
  return object.prototype.tostring.call(obj) === '[object object]'
}

/*
 * 深拷贝,仅针对对象
 * params {object} obj
 */
function deepcopy (obj) {
  if (!isobject(obj)) {
    throw new typeerror(`${obj} is not a object`)
  }
  
  let res = {}
  for (let key in obj) {
    if (obj.hasownproperty(key)) {
      res[key] = isobject(obj[key]) ? deepcopy(obj[key]) : obj[key]
    }
  }
  
  return res
}

虽然解决了大部分json.parse(json.stringify(oldobj))的问题,但依然无法解决循环引用的问题。

let oldobj = {}
oldobj.obj = oldobj

let newobj = deepcopy(oldobj)

newobj.obj === oldobj.obj // true

解决循环引用


其实只要将已被拷贝的对象存储下来,每次递归之前都检查一遍该对象是否已经被拷贝,就可以解决循环引用的问题了。

/*
 * 深拷贝,仅针对对象
 * params {object} obj
 */
function deepcopy (obj, list = new weakmap()) {
  if (list.has(obj)) {
    return list.get(obj)
  }
  
  if (!isobject(obj)) {
    throw new typeerror(`${obj} is not a object`)
  }
  
  let res = {}
  list.set(obj, res)
  for (let key in obj) {
    if (obj.hasownproperty(key)) {
      res[key] = isobject(obj[key]) ? deepcopy(obj[key], list) : obj[key]
    }
  }
  
  return res
}

这样循环引用的问题就解决啦,是不是很简单。

let oldobj = {}
oldobj.obj = oldobj

let newobj = deepcopy(oldobj)

newobj.obj === oldobj.obj // false

当然,weakmap 可能会存在兼容性问题,所以可以将 list 改成数组。