2023年8月9日
By: Chase

聊聊JS的深拷贝structuredClone

前言

在线运行js需要能qiang 在某处看到现在可以用structuredClone实现深拷贝了, 就针对它进行了一些了解和对比.

structuredClone兼容性

图-0

最新的主流浏览器都能用, 发现普遍都是22年上半年之后的版本才支持. 因为现阶段我的项目主要是electron, 浏览器版本很固定, 可以拿来用用. 传统的互联网项目还是慎用吧.

我想查查这是什么时候发布的新特性, 一搜网上一些文章说这是ES2021标准里新发布的, 但是我去查了下标准, 并没有.

之后又搜了下为什么这个不是ES2021标准, github上有个issue讨论这个, 但是我没怎么看懂. anyway, 我能用就先用用看.

测试

// 测试是否深拷贝
const testObj = { a: 1, b: { val: 2} }
const cpObj = structuredClone(testObj)

cpObj.b.val = 3
console.log('cpObj', cpObj)
console.log('testObj', testObj)

拷贝类型支持

structuredCloneJSON转化进步的一点, 是不会把一些数据搞丢.支持类型

// 测试
const obj = {
    a: undefined,
    b: /\d+/g,
    c: "hahaha"
}

console.log(structuredClone(obj))
console.log(JSON.parse(JSON.stringify(obj)))


// 传递了不能clone的数据类型会报错
obj.d = () => console.log(123)
console.log(structuredClone(obj))

性能比较

在不考虑Object key里有RegExp, 函数等的情况下, 我一般就用JSON.parse(JSON.stringify(obj))深拷贝.

或者对Object递归拷贝, lodash的cloneDeep就是基于这个思路封装的.

下面和他俩横向比较一下性能.

tips: 这里需要自己F12打开调试窗口看console.time的输出了


    // 定义一个loop测试函数, 增加总耗时
    const testPerformanceFunc = ({
        testFunc, // 测试函数
        timeLabel = 'time', // console的标签
        times = 1e6 // 循环次数 (尽量不要超过100W次, 会卡的)
    }) => {
        console.time(timeLabel)
        for (let i = 0; i < times; i +=1 ) {
            testFunc()
        }
        console.timeEnd(timeLabel)
    }


    // 测试数据
    const testObj = { a: 1, b: { val: 2 }}

    // 分别测试
    testPerformanceFunc({
        testFunc: () => JSON.parse(JSON.stringify(testObj)),
        timeLabel: 'JSON拷贝'
    })

    testPerformanceFunc({
        testFunc: () => structuredClone(testObj),
        timeLabel: 'structuredClone'
    })

    testPerformanceFunc({
        testFunc: () => _.cloneDeep(testObj),
        timeLabel: 'lodash deep clone'
    })
    

我的测试结果截图: 图-2

分别为循环10w 100w 1000w次, 可以看出structuredClone性能消耗基本上是JSON转换lodash的三倍

结论

单层结构拷贝, 继续用方便的{...obj}解构复制就可以.

复杂结构深拷贝, 从数据类型兼容性, 性能方面, 浏览器兼容多方面综合看,loadsh(或自己封装个递归)依然是最优选.

Tags: 前端