自定义深拷贝函数

简介: 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
}
目录
相关文章
|
29天前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
50 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
3月前
|
存储 JavaScript 前端开发
对象的属性方法和深浅拷贝
总结,理解对象的属性和方法对于编程是基础而重要的,而掌握深浅拷贝的差异和使用场合则是编程的高级技能,它能帮助你有效地管理数据的完整性和独立性。
19 0
|
5月前
|
设计模式 Java C++
数据结构篇:数据拷贝、深拷贝、重载与移动构造
数据结构篇:数据拷贝、深拷贝、重载与移动构造
23 0
|
6月前
|
编译器 C++
C++ 解引用与函数基础:内存地址、调用方法及声明
C++ 中的解引用允许通过指针访问变量值。使用 `*` 运算符可解引用指针并修改原始变量。注意确保指针有效且不为空,以防止程序崩溃。函数是封装代码的单元,用于执行特定任务。理解函数的声明、定义、参数和返回值是关键。函数重载允许同一名称但不同参数列表的函数存在。关注公众号 `Let us Coding` 获取更多内容。
176 1
|
6月前
函数参数传递_使用引用避免拷贝
函数参数传递_使用引用避免拷贝
28 0
|
JavaScript
如何使用js函数封装一个深拷贝?
如何使用js函数封装一个深拷贝?
|
Python
Python的赋值引用, 浅拷贝和深拷贝
Python的赋值引用, 浅拷贝和深拷贝
90 0
Python的赋值引用, 浅拷贝和深拷贝
|
存储 程序员 Python
深度解析Python的赋值、浅拷贝、深拷贝
直接赋值: 其实就是指向对象的引用(别名)。 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。但对于不可变数据类型,不会拷贝,仅仅是指向 深拷贝(deepcopy):`copy` 模块的 `deepcopy` 方法,完全拷贝了父对象及其子对象。 拷贝 就是把原数据复制一份,在复制的数据上随意改动不会影响到其原数据。也就是这里讲的深拷贝。
|
算法 Java
深拷贝一个对象会了,怎么深拷贝一个图?
在前面,我写过一篇Java的深浅拷贝,那是基于对象的拷贝,但放眼数据结构与算法中,你有考虑过怎么拷贝一个图吗?(无向图)
205 0
深拷贝一个对象会了,怎么深拷贝一个图?