深拷贝和浅拷贝

235 阅读3分钟

slice拷贝的缺点

刚开始对数组的深拷贝的理解是这样的

let arr = [1,2,3,4,5,6]
let a1 = arr.slice()
a1[0] = 2
console.log(arr, a1) // [1, 2, 3, 4, 5, 6] [2, 2, 3, 4, 5, 6]

后来听说别人这样不是“深拷贝”,自己不知道什么原因,思考后想到如果数组里面存在对象的话应该就不行了,所以

let arr = [1,2,3,{name: 'ww'},{name: 'ls'}]
let a1 = arr.slice()
a1[4].name = 'ww'
console.log(arr, a1) 
// [1,2,3,{name: 'ww'},{name: 'ww'}]
// [1,2,3,{name: 'ww'},{name: 'ww'}]

当数组中有复杂类型的时候,发现slice对数组拷贝后,改变新数组里面的复杂类型的值原数组也会改变,

然后我查了一些资料后得出结论: 当数组里全是简单类型的时候slice是改变了第一层的数据,当数组里有复杂类型的时候,slice是只能拷贝地址,所以说slice是浅拷贝

浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

  • 简单版

let copyObj = JSON.parse(JSON.stringify(oldObj))
// JSON.stringify(obj)将JSON转为字符串
// JSON.parse(string)将字符串转为JSON格式

工作中用的最多,但是同样它的缺陷也很明显,详情请看这篇文章:www.jianshu.com/p/b084dfaad…

  • 基础版

  1. 考虑简单类型和对象
// 考虑简单类型和对象
function clone(target) {
  if (typeof target === 'object') {
    var cloneTarget = {}
    for(var key in target) {
      cloneTarget[key] = clone(target[key])
    }
    return cloneTarget
  } else {
    return target
  }
}
  1. 考虑简单类型和数组
// 考虑简单类型和数组
function clone(target) {
  if (typeof target === 'object') {
    var cloneTarget = []
    for(var key in target) {
      cloneTarget[key] = clone(target[key])
    }
    return cloneTarget
  } else {
    return target
  }
}
  1. 考虑简单类型,数组,对象
function clone(target) {
  if (typeof target !== 'object') return target // 考虑简单类型
  let cloneTarget = Array.isArray(target) ? [] : {} // 考虑数组还是对象
  for (const key in target) {
    cloneTarget[key] = clone(target[key]) // 考虑嵌套
  }
  return cloneTarget
}
let str = '1'
let arr = [1, 2, 3, 4, { name: 'zs' }]
let obj = { name: 'zs', age: 19 }
let cloneStr = clone(str)
console.log(cloneStr)
let cloneArr = clone(arr)
console.log(arr)
cloneArr[4].name = 'ls'
console.log(cloneArr)
console.log(arr)
let cloneObj = clone(obj)
cloneObj.name = 'ww'
console.log(cloneObj)
console.log(obj)

这个已经基本可以满足我们最基本的要求了

  1. 考虑函数
function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m
  const paramReg = /(?<=\().+(?=\)\s+{)/
  const funcString = func.toString()
  if (func.prototype) {
    console.log('普通函数')
    const param = paramReg.exec(funcString)
    console.log('param:', param)
    const body = bodyReg.exec(funcString)
    console.log('body:', body)
    if (body) {
      console.log('匹配到函数体:', body[0])
      if (param) {
        const paramArr = param[0].split(',')
        console.log('匹配到参数:', paramArr)
        return new Function(...paramArr, body[0])
      } else {
        return new Function(body[0])
      }
    } else {
      return null
    }
  } else {
    return eval(funcString)
  }
}

其实拷贝函数直接返回就可以,因为你没有办法去修改一个函数(个人理解) 但是如果真的要深拷贝一份,那就要用上面的代码,特别感谢参考链接 上面的这个深拷贝有很多知识点,让我们一一讲解

  • eval
    • 作用: 函数会将传入的字符串当做 JavaScript 代码进行执行(将字符串str当成有效的表达式来求值并返回计算结果),
    • 语法: eval(string)
    • 参数: string一个表示 JavaScript 表达式、语句或一系列语句的字符串。表达式可以包含变量与已存在对象的属性。
    • 返回值: 返回字符串中代码的返回值。如果返回值为空,则返回 undefined 上面是mdn上给的解释 其实eval函数的主要作用是讲string转回成数组,对象……,箭头函数
数组:
const str1 = "([1, 2, [3, 4]])"
const arr = eval(str1)
console.log(arr) // [1, 2, [3, 4]]

对象:
const str2 = "({name: 'zs', age: 18})"
const obj = eval(str2)
console.log(obj) // {name: 'zs', age: 18}

箭头函数:
const str3 = "() => {console.log(1)}"
const fn = eval(str3) // 可以用于深拷贝箭头函数,函数不可以
……
  • 完整版

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数处理
  if (typeof target !== 'object' || target === null) {
    return target
  }
  // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target)

  const cloneTarget = Array.isArray(target) ? [] : {}
  hash.set(target, cloneTarget)

  // 针对Symbol属性
  const symKeys = Object.getOwnPropertySymbols(target)
  if (symKeys.length) {
    symKeys.forEach((symKey) => {
      if (typeof target[symKey] === 'object' && target[symKey] !== null) {
        cloneTarget[symKey] = cloneDeep1(target[symKey])
      } else {
        cloneTarget[symKey] = target[symKey]
      }
    })
  }

  for (const i in target) {
    if (Object.prototype.hasOwnProperty.call(target, i)) {
      cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
          ? cloneDeep1(target[i], hash)
          : target[i]
    }
  }
  return cloneTarget
}