HOWTO · JavaScript
JavaScript 元组替代方案:数组、对象与 TypeScript
JavaScript 没有原生元组类型。根据数据契约选择数组、对象、Object.freeze()、解构赋值或 TypeScript 元组。
本页内容
JavaScript 在运行时没有原生元组类型。对于坐标这类简短、有固定顺序的数据,可以使用数组并通过解构赋值读取。字段名称更能说明含义时,应使用对象。需要在运行时阻止直接修改时,可以冻结数组;需要在开发阶段检查固定长度和元素类型时,则使用 TypeScript 元组。
使用数组表示有序值
当每个位置都有稳定含义时,数组可以作为类似元组的值。以下函数按最小值、最大值的顺序返回结果:
function minMax(values) {
return [Math.min(...values), Math.max(...values)];
}
const [minimum, maximum] = minMax([7, 3, 12]);
console.log(minimum, maximum);
输出:
3 12
解构赋值为各个位置提供了清晰的局部变量名。请为调用方说明返回顺序。JavaScript 不会强制数组长度或元素类型,因此外部数据在作为位置契约使用之前必须验证。
仅在需要运行时保护时冻结数组
const 只阻止变量重新赋值,不会阻止修改变量引用的数组。Object.freeze() 冻结的是数组本身:
"use strict";
const point = Object.freeze([48.8566, 2.3522]);
console.log(point[0], point[1], Object.isFrozen(point));
// point[0] = 0; // TypeError in strict mode
// point.push(3); // TypeError
输出:
48.8566 2.3522 true
Object.freeze() 返回传入的同一个对象,并不会创建副本。如果调用方仍然拥有输入数组,应先复制再冻结,以免意外限制调用方对原数组的操作。
function asFrozenPoint(value) {
if (
!Array.isArray(value) ||
value.length !== 2 ||
!value.every(Number.isFinite)
) {
throw new TypeError("Expected two finite coordinates");
}
return Object.freeze([...value]);
}
冻结是浅层的。它会阻止修改数组的直接元素、长度和自身属性,但数组元素中的嵌套对象仍然可以修改:
const settings = Object.freeze([{ theme: "light" }]);
settings[0].theme = "dark";
console.log(settings[0].theme);
输出:
dark
因此,不应把浅层冻结的嵌套结构称为完全不可变。深度冻结需要递归访问嵌套值并处理循环引用,只有确实需要这种保证时才值得实现。
名称比位置更清楚时使用对象
当一个值包含多个字符串、布尔值、可选字段或将来可能增加的字段时,仅靠位置很容易产生误解:
function createUser() {
return { id: 42, name: "Ada", active: true };
}
const { id, name, active } = createUser();
数组适合 [x, y] 或 Object.entries() 返回的 [key, value] 这类紧凑且含义明确的组合。如果调用方必须记住索引 1 或 2 的含义,带命名属性的对象通常更安全。
Object.entries() 已经提供了有文档说明的位置组合。通过解构赋值,可以在使用处明确两个位置的含义:
for (const [key, value] of Object.entries({ theme: "light" })) {
console.log(`${key}: ${value}`);
}
输出:
theme: light
使用 TypeScript 检查元组契约
TypeScript 可以表达数组的长度、元素顺序和元素类型:
const point: readonly [number, number] = [48.8566, 2.3522];
readonly 会在 TypeScript 源码中阻止写入。编译后,该值仍是普通 JavaScript 数组,并不会自动在运行时冻结。只有同时需要静态类型检查和运行时保护时,才需要把 TypeScript 元组与 Object.freeze() 结合使用。
总结
JavaScript 数组可以按约定作为元组使用,并通过解构赋值读取。命名字段请选择对象,浅层运行时保护请选择 Object.freeze(),固定长度和类型的静态检查请选择 TypeScript 元组。