对象深度拷贝

86 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

起因

偶然从朋友那里听说,互联网寒冬的严峻格局,让躺平的我后脊发凉,近些年随着互联网的兴起,大部分年轻人涌入了进来,造成了供大于求的现象,领先企业直接让新小企业无法存活。

立个flag,好好学习

感觉未来两年情况愈加复杂,还是要自己学习一个生存本领吧

开始

写一个深拷贝的例子吧,这个问题很久前就深入研究过。当时研究了两个方向,深度优先和广度优先。所以可以轻松写下了一个,这个应该是属于深度优先吧。 标准答案,还需要在for循环里判断一下 obj.hasOwnProperty(key) ,判断一下这个key是不是自身的属性。for in 遍历的不仅有自身属性,还有继承的属性

// 深拷贝
function copy (obj) {
  if (typeof obj !== 'object') {
    return obj
  }
  let o = {}
  if (Array.isArray(obj)) {
    o = []
  }
  for (let key in obj) {
    o[key] = copy(obj[key])

  }
  return o
}
let obj = {
  name: "tom",
  child: {
    name: "www",
  },
  aa: [1, 3, { name: 't' }]
}
let o = copy(obj)
console.log(o)

如果考虑到属性中包含函数和属性中有相同引用时,上面的方式显然是经不起推敲的,只是入门级的深考呗,只是为了完成任务而完成, 有如下数据

function xxx () {
  console.log('xxx')
}
let s = {
  o: 'jjjj'
}
let obj = {
  name: "tom",
  child: {
    name: "www",
  },
  d: { o: 'jjjj' },
  b: s,
  c: s,
  a () {
    console.log('obj')
  },
  f1: xxx,
  f2: xxx,
  aa: [1, 3, { name: 't' }]
}

显然拷贝出来的数据属性b和属性c并不是同一个引用。那么怎么复用这种引用关系呢?就是要将已经处理的数据缓存起来。利用weakMap的特性,将对象obj,当key然后,处理结果当成value,如果下次遇到同样的属性,就直接复用之前拷贝的对象,这样才实现了真正意义结构上的拷贝

// 深度优先拷贝
function copy (obj, visited = new WeakMap()) {
  if (visited.has(obj)) {
    return visited.get(obj)
  }
// 此处
  if (typeof obj !== 'object') {
    return obj
  }
  let o = {}
  if (Array.isArray(obj)) {
    o = []
  }
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      o[key] = copy(obj[key], visited)
      visited.set(obj, o)
    }
  }
  return o
}

函数的处理,讲道理,我是不认为需要特殊处理函数的,因为函数本身就应该是纯函数,直接指向原来的函数也无所谓,复制函数很麻烦也很危险。此处忽略

  if (typeof obj === 'function') {
    let res
    let str = obj.toString()
    if (str.includes('function')) {
      res = eval(`(${str})`)
    } else {
      res = eval(`(function ${str})`)
    }
    visited.set(obj, res)
    return res
  }

广度优先

广度优先

image.png

结论

深度优先算法占用内存少,但是速度较慢,广度优先算法占用内存多,速度较快