JavaScript Set.intersection 的顺序:只加一个无关成员,交集为什么倒过来了
筛选可选标签时,左侧Set按页面希望的顺序保存blue、red,右侧保存允许出现的red、blue。调用intersection后,看起来保住了左侧顺序。后来只向左侧加入一个右侧根本没有的green,交集仍是那两种颜色,显示顺序却反了。
这不是随机排序。对本例两个普通Set,intersection会比较两边大小:左侧大小小于或等于右侧时,按左侧次序检查;左侧更大时,改按右侧次序检查。返回集合的插入顺序跟着被遍历的一侧,因此不能把成员的交集关系误当成页面顺序保证。
以下程序只使用固定字符串和内存集合。保存为demo.mjs,执行node demo.mjs,已在Node.js v24.19.0验证。测试同时观察集合成员、结果顺序和输入是否被改变;旧运行时应先确认Set.prototype.intersection存在。
AI模型生成的概念插图:两条不同长度的轨道汇入同一出口,用来提示遍历来源影响次序;不是集合内容清单或软件截图。
完整可运行程序
import assert from 'node:assert/strict';
const preferred = new Set(['blue', 'red']);
const allowed = new Set(['red', 'blue']);
const equalSize = [...preferred.intersection(allowed)];
assert.deepEqual(equalSize, ['blue', 'red']);
console.log('equal sizes:', equalSize.join(','));
preferred.add('green');
const largerLeft = [...preferred.intersection(allowed)];
assert.deepEqual(largerLeft, ['red', 'blue']);
console.log('larger left:', largerLeft.join(','));
assert.equal(largerLeft.includes('green'), false);
const explicitOrder = [...preferred].filter(x => allowed.has(x));
assert.deepEqual(explicitOrder, ['blue', 'red']);
console.log('explicit left order:', explicitOrder.join(','));
assert.deepEqual([...preferred], ['blue', 'red', 'green']);
assert.deepEqual([...allowed], ['red', 'blue']);
assert.equal(preferred.intersection(new Set()).size, 0);
console.log('inputs unchanged; empty intersection checked');本次实际输出
equal sizes: blue,red larger left: red,blue explicit left order: blue,red inputs unchanged; empty intersection checked
无关成员改变的是算法分支
第一行equal sizes是blue,red。两边都只有两个成员,算法选择左侧,先检查blue再检查red。第二行larger left变为red,blue,因为左侧增加green后有三个成员,而右侧仍有两个,算法转而按右侧的插入顺序寻找共同项。
green没有进入结果,断言也明确检查它不存在。变化来自size比较,并非green本身参与了交集,也不是系统给颜色名称重新排序。若只验证结果包含blue和red,就会漏掉用户界面真正遇到的顺序变化。
成员相同,不代表序列相同
集合意义上的交集不关心先后;把结果展开成数组、序列化成列表或依次绘制时,迭代顺序就变成了可见行为。测试应按需求分开:只验证成员时检查集合关系;承诺稳定顺序时则要检查完整数组,不能把两类断言混用。
等长分支选择调用方,这一点尤其容易被小样本掩盖。开发阶段两边恰好同样大,可能一直表现得像保留左侧次序;上线后某一侧多了无关条目,才暴露另一分支。保留等长、左侧更大和空交集样本,能把这种变化提前固定下来。
把页面排序写在自己的规则里
第三行explicit left order重新得到blue,red。这里明确遍历preferred的数组,并以allowed.has作为成员条件。对于本例普通Set,这样表达的是“按左侧顺序保留允许项”;返回值是数组,正好可以交给列表显示。
最后的断言确认两个输入仍保持预期内容,说明intersection本身创建了结果,没有替我们调整原集合。若需要按优先级或本地化名称排序,应在筛选后应用明确的比较规则,而不是让集合大小碰巧决定展示。
本例没有自定义has、keys或size,也没有在调用过程中增删成员。支持集合式接口的自定义对象可能带有读取副作用,不宜直接套用这个简化观察。排查时先确认操作数确实是预期容器,再记录两侧size与插入次序,通常就能找到排序变化的触发点。
官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,全部断言通过,退出状态为零。


