JavaScript 对象遍历方法及其遍历顺序的总结

JavaScript 对象遍历方法及其遍历顺序的总结

最近看了《你不知道的 JavaScript(下卷)》,看到了遍历顺序相关的内容(P240),于是作出修改和补充。
2022 / 09 / 19

00. 从规范中定义的算法说起

在 ES6 之前,一个对象属性(键)的列出顺序依赖于浏览器的具体实现,并未在规范中定义。
多数引擎采用的是按照 创建顺序 进行枚举。

ES6 规范新增了一些算法的定义,来规范对象属性的列出顺序。包括:

  • [[OwnPropertyKeys]]
  • [[Enumerate]](于 ES2016 / ES7 废除),变更为抽象方法 EnumerateObjectProperties

下面分别介绍这两种算法。


01. [[OwnPropertyKeys]]

1.1 列出属性的范围

这个算法会产生对象的 所有实例属性(即自身拥有的,而非继承来的)。

  • 包括 字符串属性 和 Symbol 符号属性。
  • 无论是否可枚举。

1.2 使用该算法的方法

这个算法只对以下方法有保证:

  • Reflect.ownKeys(..)
    • 返回由 目标对象 自身的属性键 组成的数组。
    • 相当于Object.getOwnPropertyNames()Object.getOwnPropertySymbols() 得到的两个数组进行拼接。
  • Object.getOwnPropertyNames(..)
    • 返回由 目标对象 自身的字符串属性键 组成的数组。
  • Object.getOwnPropertySymbols(..)
    • 返回由 目标对象 自身的 Symbol 属性键 组成的数组。
  • Object.assign(target, ...sources)
    • 将所有 可枚举的 自身的属性 从 一个或多个源对象 复制到 目标对象,返回修改后的对象。
    • 其中对于每一个源对象,都使用 [[ownPropertyKeys]] 算法枚举(列出)其属性键,并过滤出可枚举的属性

1.3 列出顺序

这个算法定义了一个对象属性的列出顺序:

  1. 先按照 数字上升的排序,枚举所有整数属性
  2. 再按 创建顺序 枚举其余的 字符串属性
  3. 最后按 创建顺序 枚举拥有的 Symbol 符号属性

其中,整数属性的定义为:

  • +0 <= parseFloat(key) < 2^32 - 1 (最大安全整数)
  • 不作任何修改便可以与一个整数值相互转换
String(Math.trunc(Number("49"))); // "49",相同,整数属性
String(Math.trunc(Number("+49"))) ; // "49",不同于 "+49" ⇒ 不是整数属性
String(Math.trunc(Number("1.2"))) ; // "1",不同于 "1.2" ⇒ 不是整数属性  

1.4 举例

let o = {
    
    };
o[Symbol("s2")] = "foo";
o[Symbol("s1")] = "bar";
o.b = "bbbbb";
o.a = "aaaaa";
o[2] = true;
o[1] = true;

Reflect.ownKeys(o); // ['1', '2', 'b', 'a', Symbol(s2), Symbol(s1)]
Object.getOwnPropertyNames(o); //  ['1', '2', 'b', 'a']
Object.getOwnPropertySymbols(o); // [Symbol(s2), Symbol(s1)]
Object.assign({
    
    },obj); 
// {1: true, 2: true, b: 'bbbbb', a: 'aaaaa', Symbol(s2): 'foo', Symbol(s1): 'bar'}

在这里插入图片描述


02. for..in

for..in 使用 [[enumerate]](ES6) / EnumerateObjectProperties (ES7+) 来遍历对象属性。

[[enumerate]] 于 ES2016 / ES7 中废弃,更换为使用抽象方法 EnumerateObjectProperties
同时,在 ES2016 中还废弃了 Reflect.enumerate(..) 方法。

2.1 列出属性的范围

会产生对象 实例上和原型链上(即自身拥有的以及继承来的属性) 的 可枚举属性
且只产生 字符串属性,忽略 Symbol 属性。

2.2 列出顺序

ES2015 - ES2019

ES2015 中,由于兼容性原因,[[enumerate]] 并未规定对象属性的列出顺序,可以观察到的顺序和**具体的浏览器实现相关。
即规范只限制了列出属性的范围,而没有限制列出的顺序。

ES2016 中,使用抽象方法 EnumerateObjectProperties 替代了 [[enumerate]],但同样没有规定对象属性列出的顺序。

ES2020

ES2020 开始,即使是较旧的操作如 for..inObject.keys 也需要遵循属性顺序(property order,即 [[OwnPropertyKeys]] 中定义的顺序)。


03. Object.keys()

规范定义, Object.keys() 使用 EnumerableOwnProperties 抽象方法产生对象属性列表。

  • 在这个抽象方法当中,通过调用 [[OwnPropertyKeys]] 算法取得拥有的所有键的列表。
  • 然后过滤掉 不可枚举[[Enumerable]] === false)的属性 和 Symbol 的属性。
  • 在 ES2020 前内部可能会将列表重新排列来与 for..in 的属性顺序相匹配。

与此相似,

  • Object.values()
  • Object.entries()
  • JSON.stringify()(转换对象为 JSON,遍历需要使用的对象属性列表)

也是使用同样的抽象方法 EnumerableOwnProperties 来生成属性 / 值的列表。

3.1 列出属性的范围

Object.keys() 会返回由目标对象的 实例上的(自身拥有的属性,而非继承来的)可枚举属性 组成的数组。
且只产生 字符串属性,忽略 Symbol 属性。

3.2 列出顺序

属性顺序与 for..in 保持一致。


04. For…of

for…of 可以遍历可迭代对象,即实现了 Symbol.iterator 方法的对象。

4.1 列出顺序

遍历的顺序为 调用 Symbol.iterator 工厂方法返回的迭代器中, next() 方法的返回值顺序。


05. 总结

  1. ES6 前没有明确地规范 对象属性的列出顺序。
  2. ES6 定义了新的算法 [[ownPropertyKeys]],规定了对象属性的列出顺序。
    • 使用该算法的方法:
      • Reflect.ownKeys()
      • Object.getOwnPropertyNames()
      • Object.getOwnPropertySymbols()
      • Object.assign()
    • 此时为了兼容性,for..inObject.keys()JSON.stringify() 一系列方法的属性顺序没有进行规定,仍然是由浏览器自行实现。不过这些方法的属性顺序表现一致。
  3. ES2020 开始,for..inObject.keys() 这些旧方法也需要遵循 [[ownPropertyKeys]] 中定义的属性顺序。
    在这里插入图片描述

参考文章

Object.keys(…)对象属性的顺序?
一起学规范系列 —— Object.keys() 的顺序是如何定义的?
【JS】for in和for of的前世今生,从Symbol和Iterator讲起

[更新于 2022 / 09 / 19]
ES6 是否为对象属性引入了明确定义的枚举顺序?
EnumerateObjectProperties 规范定义

猜你喜欢

转载自blog.csdn.net/weixin_50290666/article/details/124219626
今日推荐