ECMAScript 6集合遍历机制分析

2021-03-10 09:20张晓静
电子技术与软件工程 2021年20期
关键词:数据结构调用内置

张晓静

(92981 部队 北京市 100161)

ECMAScript 是 通 过Ecma 国 际(Ecma International, 前身为欧洲计算机制造商协会,European computer manufacturers association)制订的ECMA-262 协议标准化的脚本程序设计语言[1],JavaScript 的语法和语义是ECMAScript 规范的实现。自1997年起至今ECMAScript 不断更新并发布了多个版本。2011年ECMAScript 5.1 版本(ES5)成为了ISO 国际标准(ISO/IEX 16262),并在万维网上得到广泛应用。2015年ECMAScript 6 版本(ES6)是继ES5 之后的一次重大改进,旨在让JavaScript 成为企业级开发语言而不再局限于浏览器端,而后发布的版本都是在ES6 的基础上新增较少特性。ES6 经迅速普及,目前基本已成为业界标准。

遍历是一种循环装置,能够顺序访问集合中每一个元素。编程语言往往通过提供一种机制来支持遍历集合,将内部实现细节隐藏在类的私有部分[2],开发人员只需调用公共方法,而对于架构师级别的开发人员来说,了解底层实现是必要的。本文旨在通过剖析ES6 遍历机制的内部实现细节,来为深入理解ES6 语言设计思路和未来工作提供坚实的理论基础。

1 遍历语句和方法

1.1 遍历语句对比分析

遍历语句通常在大括号内部写入每次循环需要做什么,通过continue、break 语句改变遍历正常进行过程。ES5 中集合主要是Array(数组)和Object(对象),遍历语句主要有for、while、dowhile 及for-in,表1 为ES5 及ES6 的遍历语句对比分析。

表1:遍历语句对比

for 需要规定指针赋值语句、执行条件表达式和计数器更新表达式,如果是多层嵌套循环,则每一层都需要这些表达式或语句。for、while、do-while 语句是最传统和简易的遍历语句,底层处理功能较少,语法相对繁琐,但这样详细的控制方式使得输出方式更灵活。

for-in 语句遍历输出对象的可枚举属性,包括其自身属性和原型链上继承的属性,所有属性都会转换为字符串输出。其遍历顺序是一个相对复杂的规则:首先,遍历对象自身的可枚举属性,如果是非负整数属性或能够转换为非负整数的字符串属性,则先按照数值的升序顺序遍历,接着对不能转换为非负整数的字符串属性,按照元素加入对象的顺序遍历;然后,依照上述规则逐层遍历对象原型链上的可枚举属性,直到顶层Object 的可枚举属性也遍历结束。for-in 的语法较为简洁,但遍历顺序不是直观的元素加入顺序,且会遍历原型链上的可枚举属性,这不一定是开发人员需要的。图1为for-in 遍历对象举例,输出结果顺序与上述规则一致。

图1:for-in 遍历对象举例

ES6 新增集合主要有Set、Map,以及for-of 遍历语句。for-of语句能够遍历任何可遍历对象,语法和for-in 一样简洁,而它输出顺序固定,支持多种输出形式,是ES6 标准下遍历的最佳选择。

1.2 遍历方法对比分析

传统ES5 遍历方法需要传递一个回调函数作为参数,回调函数中写入每次循环时需要做什么,并写入return 语句返回循环结果。表2 为ES5 与ES6 的遍历方法对比分析,ES5 遍历的方法主要有:forEach、map、filter、some、every;ES6 针对Array 新增了find、findIndex,并对Set、Map、Array 都提供了entries、keys 和values方法。

表2:遍历方法对比

forEach 方法可以遍历Set、Map 和Array,回调函数内return语句代表退出本次循环而不是中断遍历,中断遍历需要抛出异常,这是一种不友好的编程风格。map 方法用于映射一个新的数组,return 语句代表本次循环元素映射值。some 方法用于查找符合条件的元素, return 语句返回一个布尔值,代表当前元素是否符合条件,找到符合条件的元素时中断遍历。ES6 新增的find、findIndex 方法也是用于查找。every 方法用于测试集合元素,return 语句返回一个布尔值,代表是否通过测试,找到测试不通过的元素时中断遍历。以上方法使用场景固定,语法相对刻板,且中断遍历方式不大友好。

ES6 新增的keys、values、entries 分别是用于遍历集合元素的键名、键值、键值对,Array、Set、Map 集合都增加了这三个遍历方法,方法通过实例调用,不再以传递回调函数作为参数的方式调用。此外,Object 也新增了这三类方法。ES6 新增的遍历方法从根本上满足了集合遍历的需求。

2 ES6遍历机制

ES6 标准下的集合主要有Array、Object、Set、Map,开发人员基于实际需求,可能还需要组合使用这些集合,即自定义一个较为复杂的数据结构。处理不同的数据结构,需要一个统一的遍历接口机制。ES6 新增的Iterator(遍历器),旨在提供这样一个统一的机制,这与Java 等后端语言具有相似性。

2.1 遍历器协议

一个遍历器对象应包含一个名为next 的方法,而next 方法返回一个具有value、done 两个属性值的对象,其中value 为当前集合元素值,done 是代表遍历是否结束的布尔值,而返回一个遍历器对象的方法称为遍历器方法,这一规则称为遍历器协议。遍历器对象相当于一个指针,在遍历过程中指向当前循环的元素,调用其next 方法则移动指针,直到done 的值为true,代表遍历结束。图2为一个自定义遍历器方法。

图2:自定义遍历器方法

2.2 可遍历协议

遍历器协议只是规定了集合遍历方法的编写规则,在此情况下,想要调用一种集合的遍历器方法,要知道方法名称、然后执行得到遍历器对象、再逐步调用遍历器对象的next 方法。在集合种类多或自定义数据结构的情况下,仅有遍历器协议并不足够。

Iterable(可遍历)协议定义了一个默认的遍历器接口,约定将集合的默认遍历器方法都部署在该集合原型对象的Symbol.iterator属性上。当一个数据结构具有Symbol.iterator 属性,则认为它是可遍历的。其中,Symbol 是ES6 新增的原始数据类型,用于表示独一无二的属性,Symbol.iterator 是约定作为集合默认遍历器方法的属性名称。Symbol 类型的属性是可枚举属性,但for-in 语句遍历时排除了这类属性。可遍历协议是ES6 实现统一遍历机制的核心。

针对一个可遍历对象,底层执行遍历的过程如下:

(1)调用该对象Symbol.iterator 属性的方法,获取遍历器对象;

(2)调用遍历器对象的next 方法,获得包含value 和done 的返回值,done 的值为true 则退出循环,否则输出value 值;

(3)重复第2 步直到 done 值为true。ES6 中所有支持可遍历对象的场合,都是按照这一过程执行,例如for-of 语句。

2.3 内置遍历机制

2.3.1 数据结构的内置遍历器方法

ES6 为一些数据结构内置了遍历器方法,数据结构主要有String、Array、Set、Map,以及函数的arguments 参数类数组、文档对象模型的NodeList(节点列表)类数组,内置遍历器方法有keys、values 和entries,以及Symbol.iterator,内置的遍历器方法与默认遍历器方法根据实际需要可能是同一个方法实例。

图3 为String 等数据结构内置遍历器方法原型链。String 类型遍历时只需要输出每个字符,因此仅有Symbol.iterator 属性上部署的默认遍历器方法。Set 是元素不重复的类数组,键值与键名相同,因此Set 原型的keys 和values 属性指向同一个内置的名为values 的遍历器方法,通常遍历Set 集合期望输出的是元素值,因此values方法还应作为默认的遍历器方法,即Symbol.iterator 属性也指向values 方法。Array、Map 的遍历器方法内置模式与Set 大致相同,并根据自身特点分别指定了默认遍历器方法。这四种数据结构都有默认遍历器方法,因此它们的实例对象都是可遍历的。

图3:数据结构内置遍历器方法原型链

2.3.2 内置遍历器对象的遍历机制

ES6 针对String 等数据结构增加了内置遍历器原型对象,是隐藏在原型链内部的底层实现,内置遍历器对象通过调用内置遍历器方法生成。ES6 还实现了内置遍历器对象的遍历机制,使得内置遍历器对象本身也可遍历。例如,一个数组实例a,a 是可遍历的,a.entries()也是可遍历的。

图4 为String、Array、Set、Map 四种数据结构内置遍历器对象原型链。可以看出,ES6 设置了一个无名对象继承Object,这个无名对象仅有一个Symbol.iterator 属性,该属性对应方法的功能只是返回this,即返回对象本身,这四种数据结构的遍历器对象原型都继承了这个无名对象,同时遍历器原型本身都有一个next 方法,不同数据结构的内置遍历器原型实现不同的next 方法来规定其元素遍历顺序。

图4:内置遍历器对象原型链

这一机制使得内置遍历器对象遵循了可遍历协议,例如,一个数组实例a,使用for-of 语句遍历a.entries()时,首先,执行a.entries()获得a 的遍历器对象aItr;然后,执行aItr[Symbol.iterator](),底层调用时将从aItr 自身出发在原型链上逐层向上查找,最终找到无名对象上的Symbol.iterator 方法,该方法返回this,即执行结果仍为aItr;最后,循环执行aItr.next(),逐个获取元素值,同样地,底层调用next 方法时从自身出发在原型链上向上查找,实际调用了Array Iterator(数组遍历器)原型上的next 方法。

2.4 遍历体系结构及使用场合

for-of 是可遍历对象最常见的使用场合,此外还有数组形式解构、扩展运算符、Array.from()方法、Generator(生成器)中的yield*表达式等等。图5 表达了ES6 遍历机制的整体结构,其中,数据结构的内置遍历器方法、遍历器对象的遍历方法或自定义对象的遍历器方法都是通过Symbol.iterator 属性及其相应方法作为接口接入了ES6 遍历体系,让数据对象、遍历器对象、自定义对象都成为了可遍历的对象,于是可以在所有ES6 中支持遍历的场合被遍历,这些使用场合都会在底层执行2.2 中描述的遍历执行过程。

图5:ES6 遍历体系结构图

3 结语

相比ES5,ES6 在遍历方面新增了很多新特性,并通过可遍历协议实现了统一遍历机制,建立起完整的遍历体系,开发人员通过对自定义数据结构设置默认遍历器方法来接入这一体系,实现多场合灵活输出。生成器对象也是一种遍历器对象,而生成器对象内置遍历方法的实现方式,以及异步遍历器的实现细节,可以作为下一步研究内容。

猜你喜欢
数据结构调用内置
内置加劲环T型管节点抗冲击承载力计算
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
芯片内置测试电路的设计
基于系统调用的恶意软件检测技术研究
“翻转课堂”教学模式的探讨——以《数据结构》课程教学为例
内置管肠排列术治疗严重粘连性肠梗阻的临床分析
TRIZ理论在“数据结构”多媒体教学中的应用
《数据结构》教学方法创新探讨
利用RFC技术实现SAP系统接口通信