浅谈深拷贝

在 JavaScript 中可以使用递归实现一个深拷贝,代码如下:

const obj = {
  info: {
    person: {
      name: 'iwen',
      age: '18',
    }
  }
}

// 第一版
function deepClone(obj) {
  let result = Object.create(null)
  for (const key in obj) {
    if (typeof obj[key] === 'object') {
      result[key] = deepClone(obj[key])
    } else {
      result[key] = obj[key]
    }
  }
  return result
}

let o = deepClone(obj) 
console.log(o) // { info: { person: { name: 'iwen', age: '18' } } }
o.info.person = null
console.log(obj) // { info: { person: { name: 'iwen', age: '18' } } }

功能是实现了,但是代码看起来有点复杂,能不能简化一下,如下:

// 简化后
function deepClone(obj, args = {}) {
  for (const key in obj) {
    args[key] = obj[key]
    if (typeof obj[key] === 'object') {
      deepClone(obj[key], args[key])
    }
  }
  return args
}
let o = deepClone(obj) 
console.log(o) // { info: { person: { name: 'iwen', age: '18' } } }

我们发现代码简洁不少,功能也实现,但是总感觉哪里不对,我们测试一下:

o.info.person = null
console.log(obj) // { info: { person: null } }

怎么回事,不是说好的深拷贝,我递归也用了,怎么还会影响到原来的值,究竟是哪里出问题了,我们检查一下,发现在函数中有这样一步操作,args[key] = obj[key],试想一下如果obj[key]是一个对象,那么直接赋值就是对象的索引给赋值过去了,虽然后面我们仍然使用递归了遍历到了所有的值,但其实已经没有什么用了,之所以出现这样的错误是以为没有理解使用递归的目的,递归的目的是要将对象中的每一个值都转化为值类型然后进行赋值,所以只要避免出现将对象直接赋值就行了。除此之外,我们知道 for in 会将原型链上的属性也遍历到,所以这里可以优化一下

// 第二版
function deepClone(obj) {
  let hasOwn = Object.prototype.hasOwnProperty
  let result = Object.create(null)
  for (const key in obj) {
    if (hasOwn.call(obj, key)) {
      if (typeof obj[key] === 'object') {
        result[key] = deepClone(obj[key])
      } else {
        result[key] = obj[key]
      }
    }
  }
  return result
}

递归除了可以深拷贝之外,还有一个常见的应用就是数组的扁平化,比如有一个数组 [1, [2, [3, 4]]] 要转化为 [ 1, 2, 3, 4 ],代码如下:

var arr = [1, [2, [3, 4]]];

// 扁平化
function flatten(arr) {
  let _arr = []
  for (const item of arr) {
    if (Array.isArray(item)) {
      _arr = _arr.concat(flatten(item))
    } else {
      _arr.push(item)
    }
  }
  return _arr
}

console.log(flatten(arr)) // [ 1, 2, 3, 4 ]

思考一下,为什么在操作数组是需要给数组重新赋值一下,_arr = _arr.concat(flatten(item)),因为 concat 函数是纯函数,它不会影响原来的数组,所以需要手动重置一下,那这里能不能简化一下呢,代码如下:

// 简化后
function flatten(arr, args=[]) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      flatten(item, args)
    } else {
      args.push(item)
    }
  }
  return args
}

let a = flatten(arr) // [ 1, 2, 3, 4 ]
a = null
console.log(arr) // [ 1, [ 2, [ 3, 4 ] ] ]

是不是和深拷贝的简化版很像,不要被迷惑啦,这里答案是可以的,为什么这里又可以了,因为这里所有push到数组中都是值类型,哈哈。

你这里代码太长了,我要一行代码就实现,什么递归,别整那些没用的,好的,代码如下:

arr.toString().split(',').map(item => +item)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值