自定义深拷贝函数

154 阅读3分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

  • 对象相互赋值的一些关系,分别包括:
    • 引入的赋值:指向同一个对象,相互之间会影响;
    • 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
    • 对象的深拷贝:两个对象不再有任何关系,不会相互影响。
  • 前面我们已经可以通过一种方法来实现深拷贝了——JSON.stringify和JSON.parse:
    • 这种深拷贝的方式其实对于函数、Symbol等是无法处理的;
    • 并且如果存在对象的循环引用,也会报错的。
const obj = {
  name: 'aaa',
  friend: {
    name: 'bbb'
  },
  fn: function () {
    console.log('函数')
  }
}

const newObj = JSON.parse(JSON.stringify(obj))

obj.name = 'AAA'

console.log(obj) //{ name: 'AAA', friend: { name: 'bbb' }, fn: [Function: fn] }
console.log(newObj) //{ name: 'aaa', friend: { name: 'bbb' } }

自定义深拷贝函数:

1. 深拷贝函数 — 基本实现

function isObject(val) {
  const valType = typeof val
  return valType !== null && valType === 'object'
}

function deepClone(obj) {
  // 调用isObject(),然后判断值取反,不是object对象,则直接return值
  if (!isObject(obj)) {
    return obj
  }

  const newObj = {}

  for (const key in obj) {
    // 递归调用,解决最外层{}属性的值为对象的深拷贝问题
    newObj[key] = deepClone(obj[key])
  }

  return newObj
}

const obj = {
  name: 'aaa',
  friend: {
    name: 'bbb'
  },
  fn: function () {
    console.log('函数')
  }
}

const newObj = deepClone(obj)

obj.name = 'AAA'
obj.friend.name = 'BBB'

console.log(obj)
console.log(newObj)

2. 深拷贝函数 — 其它数据类型

const { debug } = require('console')

function isObject(val) {
  const valType = typeof val
  return valType !== null && valType === 'object'
}

function deepClone(obj) {
  // // 判断是否是一个Set类型
  if (obj instanceof Set) {
    return new Set([...obj])
  }

  // 判断是否是一个Map类型
  if (obj instanceof Map) {
    return new Map([...obj])
  }

  // 判断如果是Symbol的value, 那么创建一个新的Symbol
  if (typeof obj === 'Symbol') {
    return Symbol(obj.directives)
  }

  // 调用isObject(),然后判断值取反,不是object对象,则直接return值
  if (!isObject(obj)) {
    return obj
  }

  // 解决属性数组的问题
  const newObj = Array.isArray(obj) ? [] : {}
  for (const key in obj) {
    // 递归调用,解决最外层{}属性的值为对象的深拷贝问题
    newObj[key] = deepClone(obj[key])
  }

  // 对Symbol的key进行特殊的处理
  const objSymbolKeys = Object.getOwnPropertySymbols(obj)
  for (const key of objSymbolKeys) {
    newObj[key] = deepClone(obj[key])
  }

  return newObj
}

const s1 = Symbol('xxx')
const s2 = Symbol('yyy')

const obj = {
  name: 'aaa',
  friend: {
    name: 'bbb'
  },
  fn: function () {
    console.log('函数')
  },
  arr: ['a', 'b', 'c'],
  [s1]: 'ccc',
  [s2]: s2,
  set: new Set(['a', 'b', 'c']),
  map: new Map([
    ['a', 'aaa'],
    ['b', 'bbb'],
    ['c', 'ccc']
  ])
}

const newObj = deepClone(obj)

obj.name = 'AAA'
obj.friend.name = 'BBB'

console.log(obj)
console.log(newObj)

3. 深拷贝函数 — 循环引用

function isObject(val) {
  const valType = typeof val
  return valType !== null && valType === 'object'
}

// 设置为全局的话,每一次调用deepClone(obj)都会储存一份
// const map = new WeakMap()

function deepClone(obj, map = new WeakMap()) {
  // // 判断是否是一个Set类型
  if (obj instanceof Set) {
    return new Set([...obj])
  }

  // 判断是否是一个Map类型
  if (obj instanceof Map) {
    return new Map([...obj])
  }

  // 判断如果是Symbol的value, 那么创建一个新的Symbol
  if (typeof obj === 'Symbol') {
    return Symbol(obj.directives)
  }

  // 调用isObject(),然后判断值取反,不是object对象,则直接return值
  if (!isObject(obj)) {
    return obj
  }

  if (map.has(obj)) {
    return map.get(obj)
  }

  // 解决属性数组的问题
  const newObj = Array.isArray(obj) ? [] : {}
  // 保存一份深拷贝的newObj对象
  // newObj对象每一次递归调用都会更新一次值,但是key值不变,所以每次都是覆盖进行更新
  map.set(obj, newObj)
  for (const key in obj) {
    // 递归调用,解决最外层{}属性的值为对象的深拷贝问题
    newObj[key] = deepClone(obj[key], map)
  }

  // 对Symbol的key进行特殊的处理
  const objSymbolKeys = Object.getOwnPropertySymbols(obj)
  for (const key of objSymbolKeys) {
    newObj[key] = deepClone(obj[key], map)
  }

  return newObj
}

const s1 = Symbol('xxx')
const s2 = Symbol('yyy')

const obj = {
  name: 'aaa',
  friend: {
    name: 'bbb'
  },
  fn: function () {
    console.log('函数')
  },
  arr: ['a', 'b', 'c'],
  [s1]: 'ccc',
  [s2]: s2,
  set: new Set(['a', 'b', 'c']),
  map: new Map([
    ['a', 'aaa'],
    ['b', 'bbb'],
    ['c', 'ccc']
  ])
}

// 循环引用
obj.info = obj

const newObj = deepClone(obj)

obj.name = 'AAA'
obj.friend.name = 'BBB'

console.log(obj)
console.log(newObj)

console.log(obj.info.info)
console.log(newObj.info.info)