JS 现代化的深克隆

news2024/9/27 5:36:04

前端手写深拷贝/深克隆是一道回头率超高的笔试题,但笔试版一般不适用于生产环境,JSON 的奇技淫巧和 Lodash 的工具函数也各有缺点。

您知道吗,JS 现在有一种原生方法可以深层复制对象?

structuredClone 函数内置在 JS 运行时中:

const calendarEvent = {
  title: '攻城狮',
  date: new Date(111),
  attendees: ['Steve']
}

const copied = structuredClone(calendarEvent)

您是否注意到,上述示例中我们不仅复制了对象,还复制了嵌套数组,甚至是 Date 对象?

一切都如期工作:

copied.attendees // ["Steve"]
copied.date // Date: Wed Dec 31 1969 16:00:00
cocalendarEvent.attendees === copied.attendees // false

structuredClone 不仅可以如上操作,还可以:

  • 克隆无限嵌套的对象和数组

  • 克隆循环引用

  • 克隆各种 JavaScript 类型,比如 Date 、 Set 、 Map 、 Error 、 RegExp 、 ArrayBuffer 、 Blob 、 File 、 ImageData 等等

  • 传送任何可转移对象(transferable objects)

举个栗子,这种奇葩操作甚至也会如期工作:

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [new File(someBlobData, 'file.txt')] },
  error: new Error('Hello!')
}
kitchenSink.circular = kitchenSink

// ✅ 一切顺利,完整的深拷贝!
const clonedSink = structuredClone(kitchenSink)

1. 为什么不选择展示对象克隆呢?

注意,我们正在谈论的是深拷贝。如果您只需浅拷贝,即不复制嵌套对象或数组的副本,那么我们可以直接展开对象克隆:

const simpleEvent = {
  title: '攻城狮'
}
// ✅ 问题不大,此处没有嵌套对象/数组
const shallowCopy = { ...calendarEvent }

或者其他备胎,只要您愿意:

const shallowCopy = Object.assign({}, simpleEvent)
const shallowCopy = Object.create(simpleEvent)

虽然但是,一旦我们嵌套了元素,我们就会遭遇“滑铁卢”:

const calendarEvent = {
  title: '攻城狮',
  date: new Date(123),
  attendees: ['Steve']
}

const shallowCopy = { ...calendarEvent }

// 🚩 夭寿啦:我们同时在 calendarEvent 及其副本中添加了 Bob
shallowCopy.attendees.push('Bob')

// 🚩 天呢噜:我们同时为 calendarEvent 及其副本更新了 date
shallowCopy.date.setTime(456)

如你所见,我们没有完整拷贝该对象。

嵌套日期和数组仍然是两者之间的共享引用,如果我们想编辑那些被认为只会更新 calendarEvent 对象副本的内容,这可能会给我们带来无妄之灾。

2. 为什么不选择JSON.parse(JSON.stringify(i))呢?

它实际上是一个很棒的点子,且具有惊人的性能,但存在若干 structuredClone 解决了的短板。

如下所示:

const calendarEvent = {
  title: '攻城狮',
  date: new Date(123),
  attendees: ['Steve']
}

//  JSON.stringify 会把 date 转换为字符串
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

如果我们打印 problematicCopy,我们会看到:

{
  title: "攻城狮",
  date: "1970-01-01T00:00:00.123Z"
  attendees: ["Steve"]
}

这不是我们想要的!date 应该是 Date 对象,而不是字符串。

发生这种情况是因为 JSON.stringify 只能处理基本对象、数组和原始值。处理任何其他类型都十分佛系。举个栗子,Date 被转换为字符串。但 Set 则转换为 {}

JSON.stringify 甚至完全无视某些内容,比如 undefined 或函数。

举个栗子,如果我们使用此方法复制 kitchenSink

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [new File(someBlobData, 'file.txt')] },
  error: new Error('Hello!')
}

const veryProblematicCopy = JSON.parse(JSON.stringify(kitchenSink))

结果如下:

{
  "set": {},
  "map": {},
  "regex": {},
  "deep": {
    "array": [
      {}
    ]
  },
  "error": {},
}

我们必须删除最初为此使用的循环引用,因为如果 JSON.stringify 遭遇其中之一,就能且仅能报错。

因此,虽然如果我们的需求刚好符合其功能,这个方法自然棒棒哒,但我们可以用 structuredClone 肝一大坨事情(也就是上述我们未能做到的事情),而此方法却做不到。

3. 为什么不选择_.cloneDeep呢?

迄今为止,Lodash 的 cloneDeep 函数已经是解决此问题的一个十分常见的解决方案。

事实上,这确实能如期工作:

import cloneDeep from 'lodash/cloneDeep'

const calendarEvent = {
  title: '攻城狮',
  date: new Date(123),
  attendees: ['Steve']
}

// ✅ 一切顺利!
const clonedEvent = structuredClone(calendarEvent)

虽然但是,此时有且仅有一个警告。根据本人 IDE 中的导入成本(import cost)扩展,它会打印我导入的任何内容的 kb 成本,该函数压缩后总共有 17.4kb(gzip 压缩后为 5.3kb):

而这是假设您只导入了该函数的情况。如果您以更常见的方式导入,却没有意识到 Tree Shaking 优化并不总是如期奏效,您可能会一不小心仅针对这一函数导入多达 25kb 的数据 😱

虽然这对任何人而言都不会是世界末日,但在我们的例子中根本没有必要,尤其是浏览器已经内置了 structuredClone

4. structuredClone的短板

无法克隆函数

这会报错 —— DataCloneError 异常:

// 🚩 报错!
structuredClone({ fn: () => {} })

DOM节点

梅开二度 —— DataCloneError 异常:

// 🚩 报错!
structuredClone({ el: document.body })

5. 属性描述符,setters和getters

类似的类元数据(metadata-like)的功能也无法被克隆。

举个栗子,使用 getter 时,会克隆结果值,但不会克隆 getter 函数本身(或任何其他属性元数据):

structuredClone({
  get foo() {
    return 'bar'
  }
})
// 结果变成: { foo: 'bar' }

6. 对象原型

原型链不会被遍历或重复。因此,如果您克隆 MyClass 的实例,那么克隆对象将不再被视为此类的实例(但此类的所有有效属性都将被克隆)

class MyClass {
  foo = 'bar'
  myMethod() {
    /* ... */
  }
}
const myClass = new MyClass()

const cloned = structuredClone(myClass)
// 结果变成: { foo: 'bar' }

cloned instanceof myClass // false

7. 支持的类型的完整列表

简而言之,下述列表中未列出的任何内容都无法克隆:

JS内置函数

ArrayArrayBufferBooleanDataViewDateError 类型(那些下面具体列出),Map,仅限于普通对象的 Object(比如来自对象字面量),除了 symbol 的原始类型(又名 numberstringnullundefinedbooleanBigInt)、RegExpSetTypedArray

Error类型

  • Error

  • EvalError

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

Web/API类型

  • AudioData

  • Blob

  • CryptoKey

  • DOMException

  • DOMMatrix

  • DOMMatrixReadOnly

  • DOMPoint

  • DomQuad

  • DomRect

  • File

  • FileList

  • FileSystemDirectoryHandle

  • FileSystemFileHandle

  • FileSystemHandle

  • ImageBitmap

  • ImageData

  • RTCCertificate

  • VideoFrame

浏览器和运行时支持

这是最好的部分 —— 所有主流浏览器都支持 structuredClone,甚至包括 Node.js 和 Deno。

添加好友备注【进阶学习】拉你进技术交流群

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1334438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

极智芯 | 解读最新全球半导体设计厂商排名 英伟达一骑绝尘 中国韦尔半导体上榜

欢迎关注我,获取我的更多技术分享 大家好,我是极智视界,本文分享一下 最新全球芯片设计厂商排名 英伟达一骑绝尘 中国韦尔半导体上榜。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq 最近,TrendForc…

C# 主要语言区域

C# 教程 - 主要语言区域 - C# | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/features 目录 数组、集合和 LINQ 数组 字符串内插 模式匹配 委托和 Lambda 表达式 async/await 属性 数组、集合和 LINQ C# 和 .NET 提供了许多不同的…

I Doc View在线文档预览系统 cmd.json RCE漏洞复现

0x01 产品简介 IDocView是一个在线文档解析应用,旨在提供便捷的文件查看和编辑服务。 0x02 漏洞概述 I Doc View在线文档预览系统 cmd.json 接口处存在命令执行漏洞,攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个web服务器。 0x03 影…

(1)(1.11) SiK Radio v2(一)

文章目录 前言 1 概述 2 特点 3 状态LED灯 前言 SiK 遥测无线电是在自动驾驶仪和地面站之间建立遥测连接的最简单方法之一。本文提供了如何连接和配置无线电的基本用户指南。 3DR Radio v2(SiKRadio 的消费者版本) !Note 本页面以前的…

使用travelbook架设自己的实时位置共享服务

travelbook 是一款开源的安卓APP,它能以低功耗提供实时位置共享,它包含功能如下: 好友之间分享实时位置;记录行程轨迹;标记收藏地点; 这款软件的主要解决的问题包括: 场景1:查看老…

JVM的生命周期

1.加载(Loading): 在加载阶段,JVM会找到并加载Java字节码文件。加载阶段分为三个步骤:通过类的全限定名找到对应的字节码文件,创建一个与该类相关的Class对象,将类的静态数据结构存储在方法区中…

Ubuntu:VS Code上C++的环境配置

使用 VSCode 开发 C/C 程序 , 涉及到 工作区的.vscode文件夹下的3个配置文件(均可以手动创建) : ① tasks.json : 编译器构建 配置文件 ; ② launch.json : 调试器设置 配置文件 ; ③ c_cpp_properties.json : 编译器路径和智能代码提示 配置文件 ; …

【数据结构】——期末复习题题库(1)

🎃个人专栏: 🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客 🐳Java基础:Java基础_IT闫的博客-CSDN博客 🐋c语言:c语言_IT闫的博客-CSDN博客 🐟MySQL&#xff1a…

使用双异步后,从 191s 优化到 2s

目录 一、一般我会这样做:操作起来,如果文件比较多,数据量都很大的时候,会非常慢。 二、谁写的?拖出去,斩了!优化1:先查询全部数据,缓存到map中,插入前再进行…

如何使用宝塔面板+Discuz+cpolar内网穿透工具搭建可远程访问论坛服务

文章目录 前言1.安装基础环境2.一键部署Discuz3.安装cpolar工具4.配置域名访问Discuz5.固定域名公网地址6.配置Discuz论坛 前言 Crossday Discuz! Board(以下简称 Discuz!)是一套通用的社区论坛软件系统,用户可以在不需要任何编程的基础上&a…

怎么把C盘文件移到D盘?轻松操作的四种方法

在Windows系统中,C盘通常是系统盘,承载着系统文件和应用程序。但是,随着时间的推移,C盘可能会被占满空间,导致电脑运行缓慢或者出现错误。本文将介绍四种方法,帮助你轻松地将C盘中的文件移动到D盘&#xff…

ipv4静态路由与静态BFD联动示例

静态路由简介 定义 静态路由是一种需要管理员手工配置的特殊路由 目的 静态路由在不同网络环境中有不同的目的: 当网络结构比较简单时,只需配置静态路由就可以使网络正常工作。 在复杂网络环境中,配置静态路由可以改进网络的性能&#x…

充电桩如何选型MOS

• 充电桩是大功率 AC-DC 转换电源,用于给新能源电动汽车快速充电。 • 目前非 800V系统充电桩采用三相维也纳整流 LLC 电路,其中 PFC 整流可以采用二 极管,PFC 升压可以采用650V IGBT 或者 SJ MOSFET, LLC 采用 650V SJ MOSFET。…

设计模式(4)--对象行为(4)--迭代器

1. 意图 提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。 2. 四种角色 抽象集合(Aggregate)、具体集合(Concrete Aggregate)、抽象迭代器(Iterator)、具体迭代器(Concrete Iterator) 3. 优点 3.1 支持以不同的方式遍历一个聚合 3.2…

从PDF中提取图片

由于工作需要,要从pdf文件中提取出图片保存到本地,项目中就引用到了Apache PDFBox库。 1 什么是Apache PDFBox? Apache PDFBox库,一个用于处理PDF文档的开源Java工具。它允许用户创建全新的PDF文件,操作现有的PDF文档&#xff0…

Python 简易图形界面库easygui 对话框大全

easygui 安装 C:\> pip install easygui Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting easygui Using cached https://pypi.tuna.tsinghua.edu.cn/packages/8e/a7/b276ff776533b423710a285c8168b52551cb2ab0855443131fdc7fd8c16f/easygui-…

如何为你的网站启用HTTPS

步骤一:获取SSL/TLS证书 选择SSL证书提供商: 选择一家可信赖的SSL证书提供商。对于小型网站,JoySSL提供的免费证书是一个不错的选择。购买或申请证书: 根据你的网站需求,购买相应类型的SSL证书。证书的类型包括单域、…

电子电器架构刷写方案——General Flash Bootloader

电子电器架构刷写方案——General Flash Bootloader 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 注:文章1万字左右,深度思考者入!!! 老规矩,分享一段喜欢的文字,避免…

git命令和docker命令

1、git git是分布式的版本控制工具 git可以通过本地仓库管理文件的历史版本记录 # 本地仓库操作的命令 # 初始化本地库 git init # 添加文件到暂存区 git add . git checkout 暂存区要撤销的文件名称 # 提交暂存区文件 git commit -m 注释# 版本穿梭 # 查看提交记录 git log…

Web 开发技术

Web 开发技术 | MDN (mozilla.org)https://developer.mozilla.org/zh-CN/docs/Web 开放的 Web 为开发者提供了巨大的机遇,为了充分利用这些技术,你需要知道如何使用它们。在下方你可以找到相关 Web 技术的文档链接。 面向 Web 开发者的文档 Web 开…