JavaScript 对象属性的遍历顺序是怎样的?
简化版
现代 JavaScript 中,对象自有属性的常见遍历顺序大体是:整数索引键按升序,字符串键按创建顺序,Symbol 键按创建顺序。不同 API 是否包含不可枚举属性、继承属性和 Symbol 属性并不一样。
面试时要同时回答“顺序规则”和“遍历 API 覆盖范围”,比如 Object.keys 只返回可枚举自有字符串键,Reflect.ownKeys 返回全部自有键。
详细版
const s = Symbol("s");
const obj = {
b: 1,
2: 2,
a: 3,
1: 4,
[s]: 5
};
console.log(Object.keys(obj)); // ["1", "2", "b", "a"]
console.log(Reflect.ownKeys(obj)); // ["1", "2", "b", "a", Symbol(s)]
整数索引键优先按数字升序,然后是普通字符串键按插入顺序,最后是 Symbol 键。
不要把对象当作严格有序 Map 使用。如果业务需要稳定插入顺序和任意键类型,优先考虑 Map。
完整版教学
一、对象属性遍历要分两个问题
第一,哪些属性会被遍历出来。
第二,被遍历出来的属性按什么顺序排列。
很多回答只说“对象无序”或“按插入顺序”,都不够准确。现代规范已经定义了不少自有属性的顺序规则。
面试里要把“覆盖范围”和“顺序规则”拆开讲,答案会清楚很多。
二、属性键分三类
对象属性键主要有三类:
| 类型 | 示例 | 排序特点 |
|---|---|---|
| 整数索引键 | "0"、"2" | 按数字升序 |
| 字符串键 | "name"、"01" | 按创建顺序 |
| Symbol 键 | Symbol("id") | 按创建顺序 |
注意 "01" 通常不是整数索引键,因为它不是规范意义上的数组索引形式。
三、数字样式键会提前
const obj = {};
obj.name = "Ada";
obj[2] = "two";
obj[1] = "one";
obj.age = 28;
console.log(Object.keys(obj));
// ["1", "2", "name", "age"]
虽然 name 更早创建,但整数索引键会排在普通字符串键之前。
四、常用 API 覆盖范围不同
| API | 自有属性 | 可枚举限制 | 包含 Symbol | 包含继承 |
|---|---|---|---|---|
Object.keys | 是 | 是 | 否 | 否 |
Object.getOwnPropertyNames | 是 | 否 | 否 | 否 |
Object.getOwnPropertySymbols | 是 | 否 | 是 | 否 |
Reflect.ownKeys | 是 | 否 | 是 | 否 |
for...in | 否 | 是 | 否 | 是 |
选择 API 时要先问自己需要哪类属性。
五、for…in 会遍历继承可枚举属性
const proto = { inherited: true };
const obj = Object.create(proto);
obj.own = true;
for (const key in obj) {
console.log(key);
}
如果只想处理对象自身属性,要加判断:
if (Object.hasOwn(obj, key)) {
// own only
}
六、业务需要稳定顺序时考虑 Map
对象适合表达记录结构,Map 更适合表达有序键值集合。
const map = new Map();
map.set("name", "Ada");
map.set(2, "two");
map.set(1, "one");
console.log([...map.keys()]); // ["name", 2, 1]
Map 按插入顺序迭代,也支持对象、函数等任意键。
七、常见误区与追问
- 误区:对象属性完全无序。 现代规范对很多遍历场景定义了顺序。
- 误区:对象属性总按插入顺序。 整数索引键会优先按升序排列。
- 误区:Object.keys 能拿到所有属性。 它只拿可枚举自有字符串键。
- 误区:for…in 只遍历自身属性。 它会遍历继承链上的可枚举字符串键。
- 追问:Reflect.ownKeys 有什么特点? 它返回全部自有键,包括不可枚举和 Symbol。
- 追问:什么时候用 Map 替代对象? 需要稳定插入顺序、任意键类型或频繁增删键值时。
八、加强记忆
顺序口诀:
integer index -> string -> symbol
升序 -> 插入序 -> 插入序
API 口诀:Object.keys 常规可枚举;Reflect.ownKeys 最全自有键;for...in 会走继承链。回答时用一个数字键提前的例子,很容易把规则讲透。