内容接上一篇
以下是测试性能的环境
CPU | AMD Ryzen 9 5900HX with Radeon Graphics 八核 |
操作系统 | Windows 11 家庭中文版 (64位) |
内存 | 16GB(3200 MHz / 3200 MHz) |
Nodejs | v18.16.0 |
测试四:元素全是对象类型的数组
测试代码
import {cloneDeep, clone} from "lodash-es";
const time = 1000000;
const date = new Date();
const arr = new Array(time).fill(null);
for (let i = 0; i < arr.length; i++) {
arr[i] = {
key1: 'string',
key2: 'string2',
key3: 'string3',
key4: 'string4',
key5: 'string5',
key6: 'string6',
key7: 'string7',
key8: 'string8',
key9: 'string9',
key10: 123456789,
key11: 123456789.12345,
key12: 12345,
key13: 12345.12,
key14: null,
key15: null,
key16: undefined,
key17: undefined,
key18: {
key1: '12345,67899,234567',
key2: true,
key3: true,
key4: false,
key5: null,
key6: null,
key7: null,
key8: null,
key9: 12345,
key10: 65432,
},
key19: [1, 2, 3, 4, 5, 111, 222, 444.555, 222.11111, 333, 2222],
key20: date
};
}
let copy;
console.time('【深拷贝】JSON.parse');
copy = JSON.parse(JSON.stringify(arr));
console.timeEnd('【深拷贝】JSON.parse');
console.time('【深拷贝】lodash-es cloneDeep');
copy = cloneDeep(arr);
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(arr);
console.timeEnd('【深拷贝】自己实现的deepClone');
console.time('【深拷贝】手动for + 手动赋值');
const length1 = arr.length;
copy = new Array(length1);
for (let i = 0; i < length1; i++) {
const item = arr[i];
const itemKey18 = item.key18;
const itemKey19 = item.key19;
copy[i] = {
key1: item.key1,
key2: item.key2,
key3: item.key3,
key4: item.key4,
key5: item.key5,
key6: item.key6,
key7: item.key7,
key8: item.key8,
key9: item.key9,
key10: item.key10,
key11: item.key11,
key12: item.key12,
key13: item.key13,
key14: item.key14,
key15: item.key15,
key16: item.key16,
key17: item.key17,
key18: {
key1: itemKey18.key1,
key2: itemKey18.key2,
key3: itemKey18.key3,
key4: itemKey18.key4,
key5: itemKey18.key5,
key6: itemKey18.key6,
key7: itemKey18.key7,
key8: itemKey18.key8,
key9: itemKey18.key9,
key10: itemKey18.key10,
},
key19: [...itemKey19], // 模拟真实场景一般数组的长度不固定
key20: item.key20
};
}
console.timeEnd('【深拷贝】手动for + 手动赋值');
console.time('【浅拷贝】lodash-es clone');
copy = clone(arr);
console.timeEnd('【浅拷贝】lodash-es clone');
console.time('【浅拷贝】concat');
copy = arr.concat();
console.timeEnd('【浅拷贝】concat');
console.time('【浅拷贝】slice');
copy = arr.slice();
console.timeEnd('【浅拷贝】slice');
console.time('【浅拷贝】Object.assign()');
copy = Object.assign([], arr);
console.timeEnd('【浅拷贝】Object.assign()');
console.time('【浅拷贝】手动 扩展运算符');
copy = [...arr];
console.timeEnd('【浅拷贝】手动 扩展运算符');
console.time('【浅拷贝】手动 for');
const length = arr.length;
copy = new Array(length);
for (let i = 0; i < length; i++) {
copy[i] = arr[i];
}
console.timeEnd('【浅拷贝】手动 for');
新加了一个测试场景,就是手动为每个属性赋值,结果大大出乎我的意料(代码请看【深拷贝】手动for + 手动赋值),下面是测试结果。
测试结果
深拷贝 | 浅拷贝 | |||||||||
JSON | lodash cloneDeep | 自己实现的深拷贝 | for + 手动赋值 | loadash clone | concat | slice | Object.assign() | 扩展运算符 | for | |
100万 | 8.216s | 8.294s | 5.850s | 1.178s | 16.881ms | 14.206ms | 3.547ms | 345.892ms | 4.042ms | 13.563ms |
10万 | 659.265ms | 733.335ms | 371.223ms | 35.063ms | 2.366ms | 0.347ms | 0.374ms | 34.441ms | 0.497ms | 2.047ms |
1000 | 5.815ms | 13.723ms | 6.027ms | 0.494ms | 0.161ms | 0.012ms | 0.01ms | 0.223ms | 0.004ms | 0.034ms |
结果分析
这次新加的【深拷贝】手动for + 手动赋值 这种深拷贝性能非常高,看来这种一次完整的对象赋值,比一个属性一个属性赋值效率要高很多,基本差了5~8倍。
浅拷贝中slice和扩展运算符性能比较好。
测试五:二维数组
测试代码
import {cloneDeep, clone} from "lodash-es";
const time = 1000000;
const date = new Date();
const arr = new Array(time).fill(null);
for (let i = 0; i < arr.length; i++) {
// 共15个元素
arr[i] = [111, 2222, 3333.333, 4444.44, null, undefined, null, undefined, date, '测试', '测试用例1234', true, true, false, true]
}
let copy;
console.time('【深拷贝】JSON.parse');
copy = JSON.parse(JSON.stringify(arr));
console.timeEnd('【深拷贝】JSON.parse');
console.time('【深拷贝】lodash-es cloneDeep');
copy = cloneDeep(arr);
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(arr);
console.timeEnd('【深拷贝】自己实现的deepClone');
console.time('【深拷贝】手动for + 手动赋值');
const length1 = arr.length;
copy = new Array(length1);
for (let i = 0; i < length1; i++) {
const item = arr[i];
copy[i] = [item[0], item[1], item[2], item[3], item[4], item[5], item[6], item[7], item[8], item[9], item[10], item[11], item[12], item[13], item[14]];
}
console.timeEnd('【深拷贝】手动for + 手动赋值');
console.time('【浅拷贝】lodash-es clone');
copy = clone(arr);
console.timeEnd('【浅拷贝】lodash-es clone');
console.time('【浅拷贝】concat');
copy = arr.concat();
console.timeEnd('【浅拷贝】concat');
console.time('【浅拷贝】slice');
copy = arr.slice();
console.timeEnd('【浅拷贝】slice');
console.time('【浅拷贝】Object.assign()');
copy = Object.assign([], arr);
console.timeEnd('【浅拷贝】Object.assign()');
console.time('【浅拷贝】手动 扩展运算符');
copy = [...arr];
console.timeEnd('【浅拷贝】手动 扩展运算符');
console.time('【浅拷贝】手动 for');
const length = arr.length;
copy = new Array(length);
for (let i = 0; i < length; i++) {
copy[i] = arr[i];
}
console.timeEnd('【浅拷贝】手动 for');
新加了一个测试场景,就是手动为每个属性赋值,结果大大出乎我的意料(代码请看【深拷贝】手动for + 手动赋值),下面是测试结果。
测试结果
深拷贝 | 浅拷贝 | |||||||||
JSON | lodash cloneDeep | 自己实现的深拷贝 | for + 手动赋值 | loadash clone | concat | slice | Object.assign() | 扩展运算符 | for | |
100万 | 3.294s | 1.594s | 1.188s | 176.17ms | 8.455ms | 6.188ms | 9.102ms | 365.283ms | 4.909ms | 10.175ms |
10万 | 278.327ms | 162.958ms | 106.819ms | 17.46ms | 1.628ms | 0.34ms | 0.304ms | 23.34ms | 0.383ms | 2.643ms |
1000 | 2.399ms | 5.132ms | 2.292ms | 0.336ms | 0.191ms | 0.01ms | 0.005ms | 0.188ms | 0.004ms | 0.037ms |
结果分析
【深拷贝】手动for + 手动赋值 性能依旧炸裂。
浅拷贝扩展运算符性能很稳定。
Object.assign()在处理数组的时候真的不行。
最终总结
1. 如果能够选择浅拷贝的情况下请尽量选择浅拷贝(尤其是对象或者数组中都是基础类型,务必使用浅拷贝)。
2. 浅拷贝如果是对象请使用lodash的clone方法,数组的话请使用扩展运算符[...arr]。
3. 深拷贝中,我最后新加的测试场景【深拷贝】手动for + 手动赋值 的性能实在是太好了,所以如果能够手动赋值还是尽量手动赋值。
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
copy.push({
key1: item.key1,
key2: item.key2,
key3: item.key3,
key4: item.key4,
key5: item.key5,
})
}
4. 深拷贝中 在几千条以下的时候JSON.parse(JSON.stringify(obj)) 性能要比loadash的cloneDeep性能好。数据量大的情况下反之。
5. 如果对象并不复杂,可以选择自己手写深拷贝方法(loadash的深拷贝为了兼容很多场景做了很多判断)。
6. 如果对象(数组)转字符串越长JSON.parse(JSON.stringify(obj)) 性能越差。