前言
自 JavasSript 诞生以来,我们一直无法简单地对引用数据类型进行深拷贝。现在 HTML 规范定义了structuredClone API
,这是一个用于深拷贝的浏览器宿主内置函数(Node 和 Deno 也进行了实现)。借此机会,我们顺便来复习一下 JS 中的深拷贝和浅拷贝。
浅拷贝 vs 深拷贝
浅拷贝
对于 JavaScript 来说,基本上所有的拷贝方法都是浅拷贝,这也就意味着,如果原始对象的深层次数据发生改变,拷贝对象也会发生改变,因为他们保持了相同的引用,这一特性也经常引发许多 bug 。
常用浅拷贝方法
...
展开操作符
const original = {a:{b:1}}
const shallowCloned = {...original}
2.Object.assign()
const original = {a:{b:1}}
const shallowCloned = Object.assign({},original)
深拷贝
相对于浅拷贝,深拷贝对原戏象的所有层次的数据都进行了一次拷贝,原对象深层次数据发生改变,不会改变拷贝对象的值。
常用深拷贝方法
JSON.parse(JSON.stringify(target))
缺陷:1. 忽略函数;2. 忽略原型链; 3.会忽略值是 undefined 的属性;4. 当数据的层次很深,会栈溢出;
JSON.stringify({a:undefined,b:()=>{}})//🌟 '{}'
JSON.stringify(undefined) //undefined
const original = {a:{b:1}}
const deepCloned = JSON.parse(JSON.stringify(original))
- 手写深拷贝 —— 递归 缺陷:1. 循环引用待解决...
function getType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
function deepClone(target) {
// 处理 原始值 null、undefined、number、string、symbol、bigInt、boolean
if (typeof target !== 'object' || target === null) {
return target
}
// 处理 array
if (Array.isArray(target)) {
return target.map((e) => deepClone(e))
}
// 处理 function
if (getType(target) === 'Function') {
return eval(`(${target.toString()})`).bind(this) // function 声明需要用"("、")"包裹
}
// 拷贝日期
if(getType(target) === 'Date') {
return new Date(target.valueOf())
}
// 拷贝正则
if(getType(target) === 'RegExp') {
return new RegExp(target)
}
// 处理 map
if (getType(target) === 'Map') {
let map = new Map()
target.forEach((v, k) => {
map.set(k, deepClone(v))
})
return map
}
// 处理 set
if (getType(target) === 'Set') {
let set = new Set()
for (let val of target.values()) {
set.add(deepClone(val))
}
return set
}
// 处理 object
if (getType(target) === 'Object') {
let cloneTarget = {}
for (let key in target) {
cloneTarget[key] = deepClone(target[key])
}
return cloneTarget
}
return target
}
StructuredClone API 介绍
structuredClone
是结构化拷贝算法的实现,能够实现几乎对所有数据类型的深拷贝。目前最新版本的浏览器都已经原生支持。
语法
structuredClone(value)
structuredClone(value, { transfer })
参数
-
value
想要拷贝的对象。
-
transfer
(可选的)可转移的对象: 为一个数组,其中的值将被移动到新的对象,而不是拷贝至新的对象。
返回值
返回传入对象深拷贝之后的对象。
异常
如果传入值是不能进行深拷贝的对象,则抛出DATA_CLONE_ERR
错误。
限制
Function
和DOM
对象,如果对象中含有,将抛出DATA_CLONE_ERR
异常。- 不保留
RegExp
对象的lastIndex
字段。 - 不保留属性描述符,
setters
以及getters
(以及其他类似元数据的功能)。例如,如果一个对象用属性描述符标记为read-only
,它将会被复制为read-write
。 - 不保留原形链。
示例
- 拷贝普通对象
const original = {a:{b:1}}
const cloned = structuredClone(original)
console.log(cloned) // {a:{b:1}}
console.log(origin.a===cloned.a) // false
- 拷贝函数
const original = {a:()=>{}}
const cloned = structuredClone(original)
// Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': ()=>{} could not be cloned.
- 拷贝带原型对象
原对象的原型链消失了
兼容性
目前最新版的主流浏览器都支持 structuredClone API
Firefox >= 94; Chrome(Edge) >=98; Safari >= 15.4;
可引入 core-js
兼容库 core-js#structuredclone,兼容旧版本。