为什么推荐用 structureClone 在 JavaScript 做深拷贝?

2024年1月28日

💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容

在前端面试的题目中,介绍「浅拷贝与深拷贝」差别,以及「手写潜拷贝与深拷贝」都是很常会考的题目,有兴趣练习的读者可以参考《[Medium] 手写浅拷贝(shallow copy) 和深拷贝(deep copy)》

因为刚好读到 builder.io 的 Steve 写的《Deep Cloning Objects in JavaScript, the Modern Way》,想说可以进一步摘要该篇,来更完整讲述深拷贝的概念。 Steve 这篇的一个重点,在谈论为什么推荐用 structureClone 在 JavaScript 做深拷贝。

深拷贝的定义是,当拷贝时,两个物件具有相同的属性名称和顺序,且每个属性的值也具有相同的结构,且在原型链也具有相同的结构。 Steve 给的例子如下,可以看到,拷贝完 copiedcalendarEvent 的物件属性都相同,只是 cocalendarEvent.attendees === copied.attendees 会是 false

const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"],
};

// 😍
const copied = structuredClone(calendarEvent);

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

《手写浅拷贝(shallow copy) 和深拷贝(deep copy)》有提到,要深拷贝,可以有几个方法,一个是用JSON.parse(JSON.stringify(obj)) 另一个是用structuredClone(obj)。而在面试中,比较常会问到如何自己手写 Lodash 的 _.cloneDeep(obj)

在 Steve 的文章中,他推荐使用原生的 structuredClone 函式,基本上如 Steve 所说,现在如果写 JavaScript 要做深拷贝,structuredClone 都是最推荐的。你可能会问为什么呢?

首先如《手写浅拷贝(shallow copy) 和深拷贝(deep copy)》 提到的,JSON.parse(JSON.stringify(obj)) 的问题在于,没办法处理 JavaScript 原生类别、纯数组与纯物件以外的东西,所以假如今天物件当中有放Set,则会被转换成{},这显然不理想。

而 Lodash 的_.cloneDeep(obj) 虽然也能有效处理各种类别的东西,只是一个问题是,Lodash 一贯以来被人诟病导入的成本太高了,如果用

import cloneDeep from "lodash/cloneDeep"

会占用 17.4kb (或者 5.3kb gzipped)。

如果是用

import { cloneDeep } from "lodash"

直接从lodash 引入则会是 71.5kb (或 25.2kb gzipped)。

从效能的角度来讲,如果可以用更少成本的方式,做到一样的事情,当然会选择用成本少的。即使现在有 lodash-es 占用更小的包体积空间,但额外引入还是多的。如果用原生的 structuredClone 就能免去这问题。

当然,structuredClone 遇到某些东西还是没办法直接拷贝,例如函式作为键的时候,或者 DOM 节点作为值的时候,都会遇到 DataCloneError。在 Steve 的原文中有提到更细的点,有兴趣的读者可以再自行阅读。

目前 structuredClone 在各大执行环境都已经被支援,包含 Chrome、Edge、Safari、Node.js、Deno。不过在 workers 的支援度上,有一些浏览器与 Node.js 还没支援。除非你要用到 workers,不然现在要做深拷贝,structuredClone 是推荐首选。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們