为什么推荐用 structureClone 在 JavaScript 做深拷贝?
2024年1月28日
在前端面试的题目中,介绍「浅拷贝与深拷贝」差别,以及「手写潜拷贝与深拷贝」都是很常会考的题目,有兴趣练习的读者可以参考《[Medium] 手写浅拷贝(shallow copy) 和深拷贝(deep copy)》 。
因为刚好读到 builder.io 的 Steve 写的《Deep Cloning Objects in JavaScript, the Modern Way》,想说可以进一步摘要该篇,来更完整讲述深拷贝的概念。 Steve 这篇的一个重点,在谈论为什么推荐用 structureClone 在 JavaScript 做深拷贝。
深拷贝的定义是,当拷贝时,两个物件具有相同的属性名称和顺序,且每个属性的值也具有相同的结构,且在原型链也具有相同的结构。 Steve 给的例子如下,可以看到,拷贝完 copied
跟 calendarEvent
的物件属性都相同,只是 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
是推荐首选。