JavaScript API性能对比系列二:深拷贝和浅拷贝性能对比

以下是测试性能的环境

CPUAMD Ryzen 9 5900HX with Radeon Graphics 八核
操作系统Windows 11 家庭中文版 (64位)
内存16GB(3200 MHz / 3200 MHz)
Nodejsv18.16.0

测试一:元素全是基础类型的数组

测试代码

import {cloneDeep, clone} from "lodash-es";

const time = 1000 * 10000;
const val = 'abcdefghijklmnopqrstuvwxyz';
const arr = new Array(time).fill(val);

let copyArr = [];
console.time('【深拷贝】JSON.parse');
copyArr = JSON.parse(JSON.stringify(arr));
console.timeEnd('【深拷贝】JSON.parse');

copyArr = [];
console.time('【深拷贝】lodash-es cloneDeep');
copyArr = cloneDeep(arr);
console.timeEnd('【深拷贝】lodash-es cloneDeep');

copyArr = [];
// 这个应该算浅拷贝,不过因为是基础类型,就划到深拷贝里了
console.time('【深拷贝】手动 for');
const length = arr.length;
copyArr = new Array(length);
for (let i = 0; i < length; i++) {
    copyArr[i] = arr[i];
}
console.timeEnd('【深拷贝】手动 for');

// 自己实现一个深拷贝,不考虑一些特殊场景
function deepClone(source) {
    if (typeof source !== 'object' || source == null) {
        return source;
    }
    const target = Array.isArray(source) ? [] : {};
    for (const key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source[key] === 'object' && source[key] !== null) {
                target[key] = deepClone(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
copyArr = [];
console.time('【深拷贝】自己实现的deepClone');
copyArr = deepClone(arr);
console.timeEnd('【深拷贝】自己实现的deepClone');

copyArr = [];
console.time('【浅拷贝】lodash-es clone');
copyArr = clone(arr);
console.timeEnd('【浅拷贝】lodash-es clone');

copyArr = [];
console.time('【浅拷贝】concat');
copyArr = arr.concat();
console.timeEnd('【浅拷贝】concat');

copyArr = [];
console.time('【浅拷贝】slice');
copyArr = arr.slice();
console.timeEnd('【浅拷贝】slice');

copyArr = [];
console.time('【浅拷贝】Object.assign()');
copyArr = Object.assign([], arr);
console.timeEnd('【浅拷贝】Object.assign()');

copyArr = [];
console.time('【浅拷贝】手动 扩展运算符');
copyArr = [...arr];
console.timeEnd('【浅拷贝】手动 扩展运算符');

测试结果

                    

 深拷贝  浅拷贝 
JSONlodash cloneDeepfor自己实现的深拷贝loadash cloneconcatsliceObject.assign扩展运算符
数字999999.99999

1000万4.020s479.56ms94.122ms1.93s185.31ms37.38ms35.06ms8.198s73.90ms
10万22.823ms6.619ms2.079ms12.044ms1.907ms0.334ms0.372ms24.024ms0.411ms
10000.272ms0.638ms0.162ms0.301ms0.07ms0.008ms0.005ms0.241ms0.004ms
数字11000万613.693ms336.968ms57.705ms1.978s92.962ms72.879ms60.213ms5.626s56.124ms
10万5.207ms5.819ms1.663ms11.275ms1.661ms0.404ms0.32ms23.207ms0.305ms
10000.118ms0.606ms0.16ms0.338ms0.071ms0.008ms0.004ms0.227ms0.006ms

字符串

abcdefghijklmnopqrstuvwxyz

1000万3.938s355.628ms96.017ms2.092s129.239ms128.126ms52.62ms5.353s84.042ms
10万12.666ms5.993ms1.729ms10.609ms1.845ms0.306ms0.319ms23.48ms0.373ms
10000.169ms0.625ms0.174ms0.424ms0.122ms0.011ms0.011ms0.164ms0.005ms

结果分析

从测试结果上来看,浅拷贝的效率要比深拷贝高很多,所以如果数组中都是基础数据类型的化建议直接使用浅拷贝。

如果数组中都是基础数据类型,那么无脑浅拷贝就可以了。

数组深拷贝的时候在数据量大的时候loadash的cloneDeep性能比较好,如果数据量小的时候JSON API比较快。

JSON API的速度和数组转字符串(JSON.stringify)的长度有明显的关系。

浅拷贝中Object.assign太拉垮了。

测试二:属性全是基础类型的对象

测试代码

import {cloneDeep, clone} from "lodash-es";

const time = 1000 * 1000;
const obj = {};
const per = time / 5;
for (let i = 0; i < per; i++) {
    obj['test1111' + i] = i;
}
for (let i = 0; i < per; i++) {
    obj['APPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP' + i] = i.toString();
}
for (let i = 0; i < per; i++) {
    obj['XXXX' + i] = true;
}
for (let i = 0; i < per; i++) {
    obj['str11334566444' + i] = 1.111222333;
}
for (let i = 0; i < per; i++) {
    obj['str' + i] = '测试一个字符串';
}

let copy;
console.time('【深拷贝】JSON.parse');
copy = JSON.parse(JSON.stringify(obj));
console.timeEnd('【深拷贝】JSON.parse');

console.time('【深拷贝】lodash-es cloneDeep');
copy = cloneDeep(obj);
console.timeEnd('【深拷贝】lodash-es cloneDeep');

// 这个应该算浅拷贝,不过因为是基础类型,就划到深拷贝里了
console.time('【深拷贝】手动 for');
Object.keys(obj).forEach((key) => {
    copy[key] = obj[key];
})
console.timeEnd('【深拷贝】手动 for');

// 自己实现一个深拷贝,不考虑一些特殊场景
function deepClone(source) {
    if (typeof source !== 'object' || source == null) {
        return source;
    }
    const target = Array.isArray(source) ? [] : {};
    for (const key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source[key] === 'object' && source[key] !== null) {
                target[key] = deepClone(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
console.time('【深拷贝】自己实现的deepClone');
copy = deepClone(obj);
console.timeEnd('【深拷贝】自己实现的deepClone');

console.time('【浅拷贝】lodash-es clone');
copy = clone(obj);
console.timeEnd('【浅拷贝】lodash-es clone');

console.time('【浅拷贝】Object.assign()');
copy = Object.assign({}, obj);
console.timeEnd('【浅拷贝】Object.assign()');

console.time('【浅拷贝】手动 扩展运算符');
copy = {...obj};
console.timeEnd('【浅拷贝】手动 扩展运算符');

测试结果

                    

深拷贝浅拷贝
JSONlodash cloneDeepfor自己实现的深拷贝loadash cloneObject.assign扩展运算符
100万1.620s605.588ms410.262ms607.865ms638.868ms930.889ms924.655ms
10万76.511ms43.793ms25.555ms49.19ms41.705ms57.76ms76.651ms
10000.468ms0.78ms0.172ms0.292ms0.318ms0.291ms2.736ms

结果分析

造1000W个属性的数据卡死了,估计实际场景也不会有这种对象,所以放弃了。

没想到浅拷贝的性能也不太理想,最好的居然是循环赋值。(如果数据都是基础数据类型,可以考虑自己循环实现拷贝)

测试三:属性中带有50%非基础类型的对象

测试代码

import {cloneDeep, clone} from "lodash-es";

const time = 1000 * 10;
const obj = {};
const per = time / 4;
for (let i = 0; i < per; i++) {
    obj['test1111' + i] = {
        name: 'name',
        sex: '男',
        age: 18,
        number: 22222222222,
        isDel: false,
        date: '2021-02-02',
    };
}
for (let i = 0; i < per; i++) {
    obj['APPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP' + i] = [1, 4, 'aaa', true, false, 99.22331, {
        name: 'name',
        sex: '男',
        age: 18,
        number: 22222222222,
        isDel: false,
        date: '2021-02-02',
    }];
}
for (let i = 0; i < per; i++) {
    obj['XXXX' + i] = true;
}
for (let i = 0; i < per; i++) {
    obj['str11334566444' + i] = i & 0 ? 10 : 1.111222333;
}

let copy;
console.time('【深拷贝】JSON.parse');
copy = JSON.parse(JSON.stringify(obj));
console.timeEnd('【深拷贝】JSON.parse');

console.time('【深拷贝】lodash-es cloneDeep');
copy = cloneDeep(obj);
console.timeEnd('【深拷贝】lodash-es cloneDeep');

// 自己实现一个深拷贝,不考虑一些特殊场景
function deepClone(source) {
    if (typeof source !== 'object' || source == null) {
        return source;
    }
    const target = Array.isArray(source) ? [] : {};
    for (const key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source[key] === 'object' && source[key] !== null) {
                target[key] = deepClone(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
console.time('【深拷贝】自己实现的deepClone');
copy = deepClone(obj);
console.timeEnd('【深拷贝】自己实现的deepClone');

console.time('【浅拷贝】lodash-es clone');
copy = clone(obj);
console.timeEnd('【浅拷贝】lodash-es clone');

console.time('【浅拷贝】Object.assign()');
copy = Object.assign({}, obj);
console.timeEnd('【浅拷贝】Object.assign()');

console.time('【浅拷贝】手动 扩展运算符');
copy = {...obj};
console.timeEnd('【浅拷贝】手动 扩展运算符');

测试结果

                    

深拷贝浅拷贝
JSONlodash cloneDeep自己实现的深拷贝forloadash cloneObject.assign扩展运算符
100万2.142s1.716s1.074s365.295ms669.646ms1.017s991.849ms
10万166.046ms159.29ms80.986ms23.6ms42.761ms62.753ms62.581ms
10001.103ms3.927ms0.947ms0.17ms0.359ms0.351ms2.853ms

结果分析

有50%的属性为引用类型的情况下,浅拷贝的整体性能要优于深拷贝。其中自己实现循环仍然遥遥领先。(如果只要浅拷贝就可以的话可以试一试)

<think>好的,我现在需要回答用户关于JavaScript深拷贝浅拷贝的区别及实现方法的问题。首先,我得回忆一下自己之前学过的相关知识,确保理解正确。 用户的问题明确,需要了解区别实现方式。根据之前提供的引用内容,引用[1][2]都提到了深拷贝浅拷贝的概念,以及实现方法。引用[3]还提到了使用JSON方法、递归第三方库如lodash。现在需要把这些信息整合起来,结构清晰地呈现出来。 首先,应该先解释基本概念。浅拷贝是复制对象的顶层属性,如果属性是基本类型,直接复制值;如果是引用类型,则复制内存地址,导致新旧对象共享同一引用,修改其中一个会影响另一个。深拷贝则是完全复制所有层级的属性,新旧对象完全独立,互不影响。这点需要明确区分,可能用例子说明会更清楚。 接下来是区别部分。根据引用[3],区别主要有拷贝层级、实现方式性能不同。需要详细说明这三个方面,比如浅拷贝只复制一层,深拷贝递归复制所有层级;实现方式上,浅拷贝可以用Object.assign或展开运算符,深拷贝则用JSON方法、递归或第三方库;性能方面,深拷贝因为递归遍历可能更耗资源,尤其是处理大对象时。 然后是实现方式。对于浅拷贝,常见的几种方法需要列出,比如Object.assign(),数组的sliceconcat方法,展开运算符...。这里需要给出示例代码,并说明这些方法的使用限制,比如只能处理一层。 深拷贝的实现方式较多。JSON.parse(JSON.stringify(obj))是最简单的方式,但存在局限性,比如无法处理函数、undefined、循环引用等。递归方法需要手动实现,遍历对象的每个属性,如果是对象则递归复制,这里可能需要示例代码说明。第三方库如lodashcloneDeep方法,用户只需调用即可,但要提醒需要安装库。另外,可能还可以提到其他方法,比如使用MessageChannel或者structuredClone(较新的API),但需注意浏览器兼容性。 注意事项部分,需要指出深拷贝的潜在问题,比如循环引用会导致递归爆栈,特殊对象如Date、RegExp等可能需要特殊处理,JSON方法不能处理函数等。这些内容引用[2][3]都有提到,需要整合进来。 最后,总结应用场景。浅拷贝适用于简单对象且不需要嵌套修改的情况,深拷贝用于复杂对象需要完全独立副本的情况。这部分帮助用户根据需求选择合适的方法。 现在需要检查是否有遗漏点,比如递归实现的代码是否正确,是否覆盖了所有实现方式,以及是否引用了正确的来源。同时,确保语言简洁,结构清晰,符合用户要求的中文回答,数学公式正确使用$...$,但在这个问题中可能不涉及数学公式,所以重点在代码块结构。</think>### JavaScript深拷贝浅拷贝的区别及实现方法 #### 1. **基本概念** - **浅拷贝(Shallow Copy)** 仅复制对象的**顶层属性**。若属性是基本类型(如`number`, `string`),直接复制值;若属性是引用类型(如`object`, `array`),则复制内存地址。因此,新旧对象会**共享嵌套的引用类型数据**,修改其中一个会影响另一个[^1][^3]。 **示例**: ```javascript const obj = { a: 1, b: { c: 2 } }; const shallowCopy = Object.assign({}, obj); shallowCopy.b.c = 3; console.log(obj.b.c); // 输出 3(原对象被修改) ``` - **深拷贝(Deep Copy)** 完全递归复制对象的所有层级属性,生成一个**完全独立的新对象**。新旧对象不共享任何引用类型数据[^2][^3]。 **示例**: ```javascript const obj = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(obj)); deepCopy.b.c = 3; console.log(obj.b.c); // 输出 2(原对象未被修改) ``` --- #### 2. **核心区别** | 特性 | 浅拷贝 | 深拷贝 | |--------------|--------------------------------------------|--------------------------------------------| | **拷贝层级** | 仅复制顶层属性 | 递归复制所有层级的属性 | | **实现方式** | `Object.assign()`、展开运算符`...` | `JSON.parse(JSON.stringify())`、递归、第三方库 | | **性能影响** | 高效 | 可能因递归遍历或复杂对象导致性能损耗 | | **共享引用** | 嵌套对象共享同一内存地址 | 完全独立,不共享任何引用 | --- #### 3. **实现方式** ##### **浅拷贝的实现** 1. **`Object.assign()`** ```javascript const shallowCopy = Object.assign({}, originalObj); ``` 2. **展开运算符`...`** ```javascript const shallowCopy = { ...originalObj }; ``` 3. **数组的`slice()``concat()`** ```javascript const arrCopy = originalArr.slice(); ``` ##### **深拷贝的实现** 1. **`JSON.parse(JSON.stringify())`** 最简单但局限性较大: - 无法处理函数、`undefined`、`Symbol`。 - 会丢失`Date`对象(转为字符串)、忽略循环引用。 ```javascript const deepCopy = JSON.parse(JSON.stringify(originalObj)); ``` 2. **递归实现** 手动遍历对象属性并递归复制: ```javascript function deepClone(obj) { if (typeof obj !== 'object' || obj === null) return obj; const result = Array.isArray(obj) ? [] : {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepClone(obj[key]); } } return result; } ``` 3. **第三方库(如`lodash`)** 使用现成的工具函数: ```javascript import { cloneDeep } from 'lodash'; const deepCopy = cloneDeep(originalObj); ``` --- #### 4. **注意事项** - **循环引用问题**:若对象存在循环引用(如`obj.a = obj`),递归方法会栈溢出,需通过缓存解决。 - **特殊对象处理**:`Date`、`RegExp`、`Map`等类型需单独处理。 - **性能权衡**:深拷贝对大型对象或频繁操作可能影响性能--- #### 5. **应用场景** - **浅拷贝**:简单对象且无需修改嵌套属性时(如配置合并)。 - **深拷贝**:需完全独立副本的场景(如状态管理、撤销操作)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值