理解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 列出顺序
这个算法定义了一个对象属性的列出顺序:
- 先按照 数字上升的排序,枚举所有整数属性。
- 再按 创建顺序 枚举其余的 字符串属性 。
- 最后按 创建顺序 枚举拥有的 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..in
、Object.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. 总结
- ES6 前没有明确地规范 对象属性的列出顺序。
- ES6 定义了新的算法
[[ownPropertyKeys]]
,规定了对象属性的列出顺序。- 使用该算法的方法:
Reflect.ownKeys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.assign()
- 此时为了兼容性,
for..in
,Object.keys()
,JSON.stringify()
一系列方法的属性顺序没有进行规定,仍然是由浏览器自行实现。不过这些方法的属性顺序表现一致。
- 使用该算法的方法:
- ES2020 开始,
for..in
,Object.keys()
这些旧方法也需要遵循[[ownPropertyKeys]]
中定义的属性顺序。
参考文章
▲ Object.keys(…)对象属性的顺序?
▲ 一起学规范系列 —— Object.keys() 的顺序是如何定义的?
▲ 【JS】for in和for of的前世今生,从Symbol和Iterator讲起
[更新于 2022 / 09 / 19]
▲ ES6 是否为对象属性引入了明确定义的枚举顺序?
▲ EnumerateObjectProperties 规范定义
上一篇: 玩转JS对象遍历,方法大揭秘!
下一篇: 玩转JS:探索数组和对象的各种遍历技巧
推荐阅读
-
通过在一阶和中阶遍历后遍历序列来还原二叉树(实现方法) - 序列的中阶遍历:9,7,3,1,5,
-
数据结构的 js 实现:树和二叉树、二叉树遍历和基本操作方法
-
[姿势估计] 实践记录:使用 Dlib 和 mediapipe 进行人脸姿势估计 - 本文重点介绍方法 2):方法 1:基于深度学习的方法:。 基于深度学习的方法:基于深度学习的方法利用深度学习模型,如卷积神经网络(CNN)或递归神经网络(RNN),直接从人脸图像中学习姿势估计。这些方法能够学习更复杂的特征表征,并在大规模数据集上取得优异的性能。方法二:基于二维校准信息估计三维姿态信息(计算机视觉 PnP 问题)。 特征点定位:人脸姿态估计的第一步是通过特征点定位来检测和定位人脸的关键点,如眼睛、鼻子和嘴巴。这些关键点提供了人脸的局部结构信息,可用于后续的姿势估计。 旋转表示:常见的旋转表示方法包括欧拉角和旋转矩阵。欧拉角通过三个旋转角度(通常是俯仰、偏航和滚动)描述头部的旋转姿态。旋转矩阵是一个 3x3 矩阵,表示头部从一个坐标系到另一个坐标系的变换。 三维模型重建:根据特征点的定位结果,三维人脸模型可用于姿势估计。通过将人脸的二维图像映射到三维模型上,可以估算出人脸的旋转和平移信息。这就需要建立人脸的三维模型,然后通过优化方法将模型与特征点对齐,从而获得姿势估计结果。 特征点定位 特征点定位是用于检测人脸关键部位的五官基础部分,还有其他更多的特征点表示方法,大家可以参考我上一篇文章中介绍的特征点检测方案实践:人脸校正二次定位操作来解决人脸校正的问题,客户在检测关键点的代码上略有修改,坐标转换部分客户见上图 def get_face_info(image). img_copy = image.copy image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # 在图像上绘制人脸检测注释。 image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) box_info, facial = None, None if results.detections: for detection in results. for detection in results.detections: mp_drawing.Drawing.detection = 无 mp_drawing.draw_detection(image, detection) 面部 = detection.location_data.relative_keypoints 返回面部 在上述代码中,返回的数据是五官(6 个关键点的坐标),这是用 mediapipe 库实现的,下面我们可以尝试用另一个库:dlib 来实现。 使用 dlib 使用 Dlib 库在 Python 中实现人脸关键点检测的步骤如下: 确保已安装 Dlib 库,可使用以下命令: pip install dlib 导入必要的库: 加载 Dlib 的人脸检测器和关键点检测器模型: 读取图像并将其灰度化: 使用人脸检测器检测图像中的人脸: 对检测到的人脸进行遍历,并使用关键点检测器检测人脸关键点: 显示绘制了关键点的图像: 以下代码将参数 landmarks_part 添加到要返回的关键点坐标中。
-
Spring IOC 的简单理解和创建对象的方法
-
[数据结构]顺序堆栈和链式堆栈的基本操作(定义、初始化、进入、退出、获取顶层元素、遍历、清空)
-
js 中的遍历方法比较:map、for......in、for......of、reduce 和 forEach 的特点和适用场景 - reduce 方法
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
Swift 中 for in 和 forEach 遍历方法的区别
-
标题:混淆概念详细解析:Python中类、对象、方法、函数和属性的区别和理解
-
理解图的遍历方法:以邻接矩阵为例的数据结构详解