JavaScript concat 展开谁:Set 可以遍历,为什么合并后仍然只占一项
Set 明明能用于 for...of,也能通过展开语法得到两个值,可是传给 concat 后,结果数组只有一个元素。可迭代不等于 concat 会逐项合并;这两个操作使用不同协议,不能只凭集合“里面有多项”判断输出。
concat 默认展开数组,把普通 Set 当作一个整体。对象还可以通过 Symbol.isConcatSpreadable 明确控制这一选择。下面保存为 demo.mjs,用 node demo.mjs 运行,只创建固定内存对象,不读取外部数据。
AI模型生成概念示意:同一合并入口可接收散开的条目,也可保留一整个分组,开关表示展开选择;非实测截图。
import assert from "node:assert/strict";
const values = new Set(["A", "B"]);
const kept = [].concat(values);
assert.equal(kept.length, 1);
assert.equal(kept[0], values);
assert.deepEqual([...values], ["A", "B"]);
console.log("Set concat length:", kept.length);
console.log("Set spread:", [...values].join(","));
const arrayLike = {0: "C", 1: "D", length: 2,
[Symbol.isConcatSpreadable]: true};
assert.equal(arrayLike[Symbol.iterator], undefined);
const opened = ["start"].concat(arrayLike);
assert.deepEqual(opened, ["start", "C", "D"]);
console.log("array-like concat:", opened.join(","));
const array = ["E", "F"];
array[Symbol.isConcatSpreadable] = false;
const grouped = ["start"].concat(array);
assert.equal(grouped.length, 2);
assert.equal(grouped[1], array);
assert.deepEqual([...array], ["E", "F"]);
console.log("array concat:", JSON.stringify(grouped));
console.log("array spread:", [...array].join(","));五行结果对应三种对象
输出依次是 Set concat length: 1、Set spread: A,B、array-like concat: start,C,D、array concat: ["start",["E","F"]],最后是 array spread: E,F。第一条断言还确认结果唯一元素就是原 Set,证明它没有被转换成另一种只有一项的集合。
第二组 arrayLike 具有零、一两个属性与 length,但没有迭代器。给它设置 Symbol.isConcatSpreadable 为 true 后,concat 按长度读取这些索引属性并加入结果,因此 start 后面是 C 和 D。完成这种展开,并不需要对象先支持 for...of。
第三组本来是普通数组。将该 Symbol 属性设为 false 后,concat 把整份数组放到结果的第二项;代码直接断言它与原数组为同一对象。最后再次使用展开语法仍得到 E、F,因为关闭 concat 展开并没有关闭数组自己的迭代器。
先决定使用哪套合并契约
如果调用方承诺交付任意可迭代值,可以先用 Array.from 转成普通数组,再按数组规则合并;如果接口承诺交付类数组,则要明确 length 与数字索引的含义。不要临时给未知对象补一个 Symbol 开关,就假定其元素来源和顺序已经可靠。
在可展开对象上,concat 依据 length 和索引属性工作,不会调用迭代器来决定有哪些值。如果某个对象同时提供这两套接口而内容不同,concat 与展开语法便可能得到不同结果。保持数据结构简单,能减少这种隐藏的协议分歧。
本例给数组设置的 Symbol 属性不会出现在 JSON.stringify 的结果中,所以打印为嵌套数组并不能展示开关本身。测试应直接检查相关属性或对象身份,而不要将一段看似普通的 JSON 输出当成所有运行时行为的完整记录。
concat 也不承诺复制被保留对象的内部内容。结果中的 Set 和关闭展开的数组仍是原对象,后续修改可能被其他引用观察到。选择是否展开与选择是否隔离可变数据是两件事,应分别写入接口约定,并用各自的断言验证。
资料核对日期:2026年10月2日。最终展示代码在 Node.js v24.19.0 独立运行并通过全部断言;结果对应文中固定输入。


