自定义深拷贝函数

简介: JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来试着自定义深拷贝函数

前情回顾

:::info
对象的深拷贝:在对象相互赋值时,两个对象不再有任何关系,不会互相影响
在前面我们就有使用过JSON.parse来实现深拷贝
:::

简单实现——JSON.parse

:::info
使用过JSON.parse来简单实现深拷贝的功能
:::

const obj = {
  name: "why",
  friend: {
    name: "kobe"
  },
  foo: function() {
    console.log("foo function")
  }
}

const info = JSON.parse(JSON.stringify(obj))
console.log(info === obj)
obj.friend.name = "james"
console.log(info)

弊端:

  • 这种深拷贝的方式其实对于函数、Symbol等是无法处理的;
  • 并且如果存在对象的循环引用,也会报错的;

    • image.png

自定义函数对深拷贝函数简单实现

function isObject(value) {
  const valueType = typeof value
  return (value !== null) && (valueType === "object" || valueType === "function")
}

function deepClone(originValue) {
  // 判断传入的originValue是否是一个对象类型
  if (!isObject(originValue)) {
    return originValue
  }

  const newObject = {}
  for (const key in originValue) {
    newObject[key] = deepClone(originValue[key]) // 对象嵌套要递归调用直到传入的值不是一个对象
  }
  return newObject
}

// 测试代码
const obj = {
  name: "why",
  age: 18,
  friend: {
    name: "james",
    address: {
      city: "广州"
    }
  }
}

const newObj = deepClone(obj)

弊端:
image.png

功能优化——对象中其他类型的深拷贝

:::info
数组/函数 类型/Symbol/Map/Set
:::

function isObject(value) {
  const valueType = typeof value
  return (value !== null) && (valueType === "object" || valueType === "function")
}

function deepClone(originValue) {
  // 判断是否是一个Set类型(实际上少见)
  if (originValue instanceof Set) {
    return new Set([...originValue])
  }

  // 判断是否是一个Map类型(实际上少见)
  if (originValue instanceof Map) {
    return new Map([...originValue])
  }

  // 判断如果是Symbol的value, 那么创建一个新的Symbol
  if (typeof originValue === "symbol") {
    return Symbol(originValue.description)
  }

  // 判断如果是函数类型, 那么直接使用同一个函数
  if (typeof originValue === "function") {
    return originValue
  }

  // 判断传入的originValue是否是一个对象类型
  if (!isObject(originValue)) {
    return originValue
  }

  // 判断传入的对象是数组, 还是对象
  const newObject = Array.isArray(originValue) ? [] : {}
  for (const key in originValue) {
    newObject[key] = deepClone(originValue[key])
  }

  // 对Symbol的key进行特殊的处理
  const symbolKeys = Object.getOwnPropertySymbols(originValue)
  for (const sKey of symbolKeys) {
    newObject[sKey] = deepClone(originValue[sKey]) // 在不同对象里面使用同一个key值是不会冲突的
  }

  return newObject
}


// 测试代码
let s1 = Symbol("aaa")
let s2 = Symbol("bbb")

const obj = {
  name: "why",
  age: 18,
  friend: {
    name: "james",
    address: {
      city: "广州"
    }
  },
  // 数组类型
  hobbies: ["abc", "cba", "nba"],
  // 函数类型
  foo: function (m, n) {
    console.log("foo function")
    console.log("100代码逻辑")
    return 123
  },
  // Symbol作为key和value
  [s1]: "abc",
  s2: s2,
  // Set/Map
  set: new Set(["aaa", "bbb", "ccc"]),
  map: new Map([["aaa", "abc"], ["bbb", "cba"]])
}

功能优化——循环引用

:::info
obj.info = obj 这种就叫做循环引用
问题:将 obj.info = obj 传入函数中,函数会不断地去调用,最后栈溢出报错
方案:使用WeakMap
:::
图解:
image.png
思路:
创建一个map对象,在循环引用第二次拷贝函数的时候可以拿到map对象,拿到之后判断原来有没有设置过newObject :

  • 如果没有则创建新的newObject
  • 如果已经有了就不再创建新的newObject而是将已有的newObject 返回
// 在参数里面创建新的map对象
// 并且当传入参数有map对象时候不会创建新的map对象,
// 可以保证 同一次循环引用时map对象唯一 
function deepClone(originValue, map = new WeakMap()) {
  ...
  if (!isObject(originValue)) {
    return originValue
  }
  // 判断原来有没有设置过newObject 
  if (map.has(originValue)) {
    return map.get(originValue)
  }

  const newObject = Array.isArray(originValue) ? []: {}
  // 将传入的originValue和newObject进行映射(设置newObject)
  map.set(originValue, newObject)
  for (const key in originValue) {
    newObject[key] = deepClone(originValue[key], map)
  }

  const symbolKeys = Object.getOwnPropertySymbols(originValue)
  for (const sKey of symbolKeys) {
    newObject[sKey] = deepClone(originValue[sKey], map)
  }
  
  return newObject
}
目录
相关文章
|
6月前
|
存储 JavaScript 前端开发
js【详解】数据类型原理(含变量赋值详解-浅拷贝)
js【详解】数据类型原理(含变量赋值详解-浅拷贝)
41 0
|
8月前
函数参数传递_使用引用避免拷贝
函数参数传递_使用引用避免拷贝
37 0
对象定义-解构-枚举属性遍历以及对象内函数
对象定义-解构-枚举属性遍历以及对象内函数
78 0
|
安全 编译器 C++
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(二)
朋友们好啊,今天终于更新了。我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地推进讲解,而不是直接把枯燥的知识点倒出来,应该会有不错的阅读体验。如果觉得不错,可以 "一键三连" 支持一下博主!你们的关注就是我更新的最大动力!Thanks ♪ (・ω・)ノ
107 0
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(二)
|
编译器 C++
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(一)
朋友们好啊,今天终于更新了。我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地推进讲解,而不是直接把枯燥的知识点倒出来,应该会有不错的阅读体验。如果觉得不错,可以 "一键三连" 支持一下博主!你们的关注就是我更新的最大动力!Thanks ♪ (・ω・)ノ
141 0
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(一)
lodash创建一个新的对象,对象的属性名是和传入对象一样,值则在函数中修改
lodash创建一个新的对象,对象的属性名是和传入对象一样,值则在函数中修改
124 0
lodash创建一个从对象中选择的属性的对象,传入函数
lodash创建一个从对象中选择的属性的对象,传入函数
83 0
|
编译器 C++
C++关于参数是对象时要调用拷贝构造函数进行传参的问题探讨
C++关于参数是对象时要调用拷贝构造函数进行传参的问题探讨
275 0

热门文章

最新文章