对象扩展运算符 { ...a } 基本可以取代 Object.assign, 实际中性能表现如何呢?
使用 Deno bench 进行测速
代码
Deno.bench("S0 = 变量赋值 a", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = a;
r;
});
Deno.bench("A0 = 修改属性 a.x = b.x", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { x: 4, y: 5, z: 6 };
a.x = b.x
a.y = b.y
a.z = b.z
});
Deno.bench("A1 = 修改属性 {...a, a}", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { ...a, x: 4, y: 5, z: 6 };
});
Deno.bench("A2 = 修改属性 assign(a, a)", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { x: 4, y: 5, z: 6 };
let r = Object.assign(a, b);
r;
});
Deno.bench("B1 = 添加属性 {...a, o}", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { ...a, a: 1, b: 2, c: 3 };
});
Deno.bench("B2 = 添加属性 { o, ...a}", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3, ...a };
});
Deno.bench("B3 = 添加属性 assign(a,b)", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = Object.assign(a, b);
r;
});
Deno.bench("C1 = 复制对象 {...a}", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = {
...a,
};
r;
});
Deno.bench("C2 = 复制对象 assign({}, a)", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = Object.assign({}, a);
r;
});
Deno.bench("D1 = 合并对象 {...a, ...b}", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = {
...a,
...b,
};
r;
});
Deno.bench("D2 = 合并对象 assign({}, a, b)", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = Object.assign(Object.assign({}, a), b);
r;
});
Deno.bench("D3 = 合并对象 assign({...a},b)", () => {
const a = { x: 1, y: 2, z: 3 };
const b = { a: 1, b: 2, c: 3 };
let r = Object.assign({ ...a }, b);
r;
});
结果
benchmark time (avg) (min … max) p75 p99 p995
-------------------------------------------------------------------------- -----------------------------
S0 = 变量赋值 a 492.79 ps/iter (462.5 ps … 11.2 ns) 487.5 ps 529.2 ps 537.5 ps
A0 = 修改属性 a.x = b.x 4.28 ns/iter (4.08 ns … 11.01 ns) 4.24 ns 5.03 ns 7.62 ns
A1 = 修改属性 {...a, a} 11.84 ns/iter (10.5 ns … 24.65 ns) 11.22 ns 18.34 ns 19.63 ns
A2 = 修改属性 assign(a, a) 28 ns/iter (25.96 ns … 42.86 ns) 27.68 ns 36.85 ns 37.97 ns
B1 = 添加属性 {...a, o} 738.88 ns/iter (648.36 ns … 1.02 µs) 818.67 ns 1.02 µs 1.02 µs
B2 = 添加属性 { o, ...a} 45.91 ns/iter (43.72 ns … 65.36 ns) 45.67 ns 57.33 ns 58.56 ns
B3 = 添加属性 assign(a, b) 38.56 ns/iter (36.5 ns … 58.95 ns) 38.4 ns 51.28 ns 53.09 ns
C1 = 复制对象 {...a} 11.05 ns/iter (10.14 ns … 33.34 ns) 10.8 ns 22.01 ns 23.3 ns
C2 = 复制对象 assign({}, a) 40.53 ns/iter (38.59 ns … 61.55 ns) 40.52 ns 51.92 ns 53.32 ns
D1 = 合并对象 {...a, ...b} 683.04 ns/iter (578.99 ns … 977.66 ns) 760.65 ns 977.66 ns 977.66 ns
D2 = 合并对象 assign({}, a, b) 75.04 ns/iter (70.82 ns … 185.35 ns) 75.49 ns 90.53 ns 93.51 ns
D3 = 合并对象 assign({...a},b) 740.79 ns/iter (632.12 ns … 1.01 µs) 813.14 ns 1.01 µs 1.01 µs
结论分析
0. 变量赋值是 ps 级别的, 直接忽略
-
对于修改已知的属性,
属性赋值性能肯定还是最好的, 但我们先忽略-
相比对手, 对象扩展运算符性能更好, 即使扩展运算符复制了新对象
-
如果 Object.assign 也要复制新对象, 参考 结论3.a
-
-
对于添加新的属性, Object.assign 性能更好
-
如果要使用扩展运算符将当前对象添加到新对象中
-
新对象特有的属性放前面性能更好
-
当前对象也存在的属性如果放在前面, 会有 '...' is specified more than once, so this usage will be overwritten. (这是因为属性值会被后面覆盖, 写了也白写)
-
-
-
在创建一个新对象的情况下,
-
对于复制一个对象, 对象扩展运算符性能更好
-
对于合并两个对象, Object.assign 的性能更好
-
混合使用也不能优化对象扩展运算符的性能
-
结合前面的结论, 可知性能卡点不在复制对象上, 而是复制后的对象上要添加新属性性能会变差
就像你看到的那样 D3(740) !== C1(11) + B3(40)
优化方案
如果平时全部使用 Object.assign() 反而不容易出现大的性能问题
对象扩展运算符写起来爽, 用法上可得多加小心了
一般情况下, 对象扩展运算符能满足大部分需求,
-
如果添加新的属性, 那么新属性需要放在对象扩展运算符前面
-
如果修改已有的属性, 那么已有的属性放在对象扩展运算符后面
-
重要!!! 永远不要在创建的新对象中使用两个(或多个)对象扩展运算符
在 ts 项目中, 我们可以得到全部属性的定义, 如果一个属性为可选, 使用 undefined 初始化后, 进行属性修改性能更佳
type A = {
x: number;
y: number;
z: number;
};
type B = {
a?: number;
b?: number;
c?: number;
};
Deno.bench("性能较差 {...a, o}", () => {
const a: A & B = { x: 1, y: 2, z: 3 };
const b: A & B = { ...a, a: 1, b: 2, c: 3 };
});
Deno.bench("将不存在的属性放在前面 {o, ...a}", () => {
const a: A = { x: 1, y: 2, z: 3 };
const b: A & B = { a: 1, b: 2, c: 3, ...a };
});
Deno.bench("将可选属性初始化为 undefined {...a, o}", () => {
const a: A & B = {
x: 1,
y: 2,
z: 3,
a: undefined,
b: undefined,
c: undefined,
};
const b: A & B = { ...a, a: 1, b: 2, c: 3 };
});
benchmark time (avg) (min … max) p75 p99 p995
--------------------------------------------------------------------------------------- -----------------------------
性能较差 {...a, o} 743.55 ns/iter (644.44 ns … 1.11 µs) 816.02 ns 1.11 µs 1.11 µs
将不存在的属性放在前面 {o, ...a} 46.56 ns/iter (43.78 ns … 116.81 ns) 46.4 ns 60.48 ns 62.6 ns
将可选属性初始化为 undefined {...a, o} 18.43 ns/iter (17.05 ns … 41.77 ns) 18.05 ns 30.33 ns 31.39 ns