准备工作
先定义好一个对象 obj,后续都用它来遍历并查看结果。
- 键的类型包括
String(含数字字符串)、Symbol - 包含不可枚举属性
- 包含原型
typescript
const proto = {
[Symbol('bb')]: Symbol('bb'), [Symbol('aa')]: Symbol('aa'),
2.2: 2.2, 20: 20, 1.6: 1.6, 5: 5, 0.6: 0.6,
cc: 'cc', bb: 'bb', false: false,
};
Object.defineProperty(proto, '3', {
value: 3,
enumerable: false,
});
Object.defineProperty(proto, 'aa', {
value: 'aa',
enumerable: false,
});
const obj = {
[Symbol('b')]: Symbol('b'), [Symbol('a')]: Symbol('a'),
2.1: 2.1, 10: 10, 5: 5, 0.9: 0.9, 0: 0, '-5': -5, 0.3: 0.3,
c: 'c', b: 'b', true: true,
};
Object.defineProperty(obj, '2', {
value: 2,
enumerable: false,
});
Object.defineProperty(obj, 'a', {
value: 'a',
enumerable: false,
});
Object.setPrototypeOf(obj, proto);
遍历顺序
从 ES6 开始,引擎底层定义了一套标准遍历顺序规则。绝大多数现代 API 都严格遵循这个顺序:
- 先遍历整数索引键,按数值从小到大升序遍历
- 再遍历
String键,按插入顺序遍历 - 最后遍历
Symbol键,按插入顺序遍历
根据 ECMAScript 规范定义,一个属性键如果满足以下两个条件,就会被 JS 底层认定为整数索引:
- 类型匹配:它是一个非负整数的字符串形式(或者能被转换为该形式的数字)。
- 范围限制:它的数值必须落在
0到2^32 - 2之间,即[0, 4,294,967,294]。
所以,除此之外的包括浮点数、负数等都会被当做 String 键,按插入顺序遍历。(注 :即使是 3.0 这种一样是浮点数,同样被当做 String 键,按插入顺序遍历)
不可枚举属性同样适用上述遍历顺序规则。
当遍历范围包括原型属性时,先遍历自有属性,再遍历原性属性(若原型对象上面还有原型则继续分层一直到顶),各自内部的遍历顺序同上。 注意:若不同层级遇到同名属性时,低层级覆盖高层级。
遍历方式
以下方法可以直接遍历对象:
for...in
迭代目标对象所有自有和继承的 String 属性键,仅包括可枚举属性。
typescript
const result = [];
for (const key in obj) {
result.push([key, obj[key]]);
}
console.log(result);
// [['0',0],['5',5],['10',10],['2.1',2.1],['0.9',0.9],['-5',-5],['0.3',0.3],['c','c'],['b','b'],['true',true],
// ['20',20],['2.2',2.2],['1.6',1.6],['0.6',0.6],['cc','cc'],['bb','bb'],['false',false]]
Object.keys()、Object.values()、Object.entries()
Object.keys() 返回一个由目标对象自有 String 属性键 组成的数组,仅包括可枚举属性。 Object.values() 返回一个由目标对象自有 String 属性值 组成的数组,仅包括可枚举属性。 Object.entries() 返回一个由目标对象自有 String 属性键值组成的二维数组,仅包括可枚举属性。
typescript
Object.keys(obj);
// ['0', '5', '10', '2.1', '0.9', '-5', '0.3', 'c', 'b', 'true']
Object.values(obj);
// [0, 5, 10, 2.1, 0.9, -5, 0.3, 'c', 'b', true]
Object.entries(obj);
// [['0', 0],['5', 5],['10', 10],['2.1', 2.1],['0.9', 0.9],['-5', -5],['0.3', 0.3],['c', 'c'],['b', 'b'],['true', true]]
Object.getOwnPropertyNames()
返回一个由目标对象自有 String 属性键组成的数组,包括不可枚举属性。
typescript
Object.getOwnPropertyNames(obj);
// ['0', '2', '5', '10', '2.1', '0.9', '-5', '0.3', 'c', 'b', 'true', 'a']
Object.getOwnPropertySymbols()
返回一个由目标对象自有 Symbol 属性键组成的数组,包括不可枚举属性。
typescript
Object.getOwnPropertySymbols(obj);
// [Symbol(b), Symbol(a)]
Reflect.ownKeys()
返回一个由目标对象自有 String 和 Symbol 属性键组成的数组,包括不可枚举属性。
typescript
Reflect.ownKeys(obj);
// ['0', '2', '5', '10', '2.1', '0.9', '-5', '0.3', 'c', 'b', 'true', 'a', Symbol(b), Symbol(a)]
Reflect.ownKeys() 等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
方式对比
| 方法 | 是否包含原型链属性 | 是否包含不可枚举属性 | 是否包含Symbol类型键 |
|---|---|---|---|
for...in |
包含 ✅ | 不包含 ❌ | 不包含 ❌ |
Object.keys() Object.values() Object.entries() |
不包含 ❌ | 不包含 ❌ | 不包含 ❌ |
Object.getOwnPropertyNames() |
不包含 ❌ | 包含 ✅ | 不包含 ❌ |
Object.getOwnPropertySymbols() |
不包含 ❌ | 包含 ✅ | 包含 ✅ |
Reflect.ownKeys() |
不包含 ❌ | 包含 ✅ | 包含 ✅ |