Enumerable - Prototype JavaScript 框架

Xunxin Prototype API

Enumerable

Enumerable 为枚举提供了大量有用的方法,从而让枚举对象可以像值的集合一样使用。它是 Prototype 的奠基石。

我们喜欢把 Enumerable 称之为一个 module:它具有一系列相关的方法,创建它的目的并不是为了独立使用,而是为了 混入(mixin):组合到其它对象中。术语“module”是从 Ruby 中借用过来的,用在这里非常贴切,因为 Enumerable 试图模仿 Ruby 中 Enumerable 模块的部分功能。

Prototype 中的许多对象都混入了 EnumerableArrayHash 是最明显的例子,但不仅于此,在一些不引人注目的地方,你也能找到它,比如 ObjectRange 以及部分与 DOM 或 Ajax 相关的对象中。

context 参数

Enumerable 中任意一个具有 iterator 参数的方法,在 iterator 参数后都可以附加一个可选的 context 参数。context 参数是 iterator 要绑定的对象,若设定该参数,iterator 中的 this 关键字将指向 context 对象。

var myObject = {}; 
['foo', 'bar', 'baz'].each(function(name, index) { 
	this[name] = index;
}, myObject); // 指定 context,this 绑定到 myObject
myObject
//-> { foo: 0, bar: 1, baz: 2} 

如果未指定 context 参数,迭代函数将会维持原有的作用域。

别名:这取决于你的意愿

与 Ruby 类似,Enumerable 同样关注你的感受,它为一些行为提供了多个名称。这主要是为了降低学习难度: 你可以根据你的技术背景选择熟悉的名称。然而,这篇文档也会尽量描述优先选择某一名称的缘由(可能是因为可读性、大众认知度、 直观性等等)。

这里是你能够在 Enumerable 中找到的别名:

  • mapcollect 是相同的。
  • find 是首选的,也可以使用 detect
  • findAllselect 是相同的。
  • includemember 是相同的。
  • entriestoArray 是相同的。

如何有效的使用?

在使用 Enumerable 的过程中,因不熟悉相应的 API,初学者经常会写出一些执行效率低下的代码。在一些常见的情形中, 使用其中一种方式其执行效率明显优于其它方式(通常代码也更具有可读性),这主要表现在以下两个方面:

collectinvokepluckeach,认真考虑一下这种情形:

在操作 Enumerable 中的所有元素时,初学者往往趋向于使用 each, 而针对 Enumerable 中的每个元素进行相同的处理并获取一个值的集合时,则使用 collect。通常情况下,这无可非议,但是,在一些特殊的情形下,使用下述方式将会使程序变得更为简捷、优雅和有效:

  • 当需要对所有元素调用相同的方法时,请使用 invoke
  • 当需要对所有元素获取同一个属性值时,请使用 pluck

rejectfindAll vs. partition

findAll/select 方法查找与指定谓词匹配的所有元素。与之相对应的是 reject 方法查找与指定谓词 不匹配 的所有元素。 假如你需要分别返回匹配和不匹配的元素,请使用 partition 方法,可以避免使用两次循环。

在自定义对象中混入 Enumerable

现在假设你已经创建了你自己的类似于集合的对象(比如说某种类型的序列或者是一些从服务器动态获取的数据范围), 你希望它能够混入 Enumerable(这也是我们推荐的),要怎么做呢?

Enumerable 模块对你的对象基本上只有一个要求:必须提供一个名为 _each(注意下划线)的方法, 该方法接收一个函数作为它唯一的参数,并且该方法还必须包含一个实际的“原生迭代”算法,然后在轮询每个元素时调用参数指定的函数, 并将当前元素传递给这个函数作为第一个参数。
译注:这里提供一个简单的样例,来源于 Prototype 内部的代码:

_each: function(iterator) {
	for (var i = 0, length = this.length; i < length; i++)
		iterator(this[i]);  //注意这一句
}

就像 each 文档所描述的那样,Enumerable 提供了所有额外层面上的处理 (如短路迭代、数字索引等等)。你只需要实现与自定义对象内部数据结构相符的迭代算法即可。

如果你仍然感到困惑,那么可以看一下 Prototype 中 ArrayHashObjectRange 对象的源代码。它们都包含了一个 _each 方法,它可以帮助你理解这个问题。

如果理解了上述概念,剩下的就是混入 Enumerable,这一步通常发生在定义自己的方法之前。这样才能够确保当需要重写 Enumerable 的方法时,重写的方法能够被实际调用。简而言之,最终的代码可能会类似于下面的结构:

var YourObject = Class.create(); 
Object.extend(YourObject.prototype, Enumerable); 
Object.extend(YourObject.prototype, { 
	initialize: function() { 
		// 构造函数
	}, 
	_each: function(iterator) { 
		// 迭代代码,每次循环时调用 iterator 
	}, 
	// 其它自定义方法,包括需要重写的 Enumerable 方法 
}); 

显然,自定义对象可以像下面这样使用:

var obj = new YourObject(); 
[...]
obj.pluck('somePropName'); 
obj.invoke('someMethodName'); 
obj.size();

方法

all

all([iterator = Prototype.K[, context]]) -> Boolean

Enumerable 中的元素全部等价于 true,则返回 true,否则返回 false。参数 iterator 是一个函数对象,该参数可选,若指定该参数, 则根据参数所定义的规则判断元素等价的 bool 值。

any

any([iterator = Prototype.K[, context]]) -> Boolean

Enumerable 中的元素有一个或多个等价于 true,则返回 true,否则返回 false。参数 iterator 是一个函数对象,该参数可选,若指定该参数, 则根据参数所定义的规则判断元素等价的 bool 值。

collect

collect(iterator[, context]) -> Array

通过 iteratorEnumerable 中的元素进行变换,返回变换后的结果集。该方法有一个别名 map。参数 iterator 是一个函数对象。

detect

detect(iterator[, context]) -> firstElement | undefined

查找第一个使 iterator 返回 true 的元素。该方法有一个别名 find。参数 iterator 是一个函数对象。

each

each(iterator[, context]) -> Enumerable

该方法是 Enumerable 的基础。它使我们能够用一种通用的方式来遍历处理所有的元素,并返回 Enumerable 以支持链式调用的编程方式。参数 iterator 是一个函数对象,用于处理 Enumerable 中的每一个元素。

eachSlice

eachSlice(size[, iterator = Prototype.K[, context]]) -> [slice...]

根据指定的大小对 Enumerable 中的元素进行分组,最后一组元素的个数可能小于指定的个数。

entries

entries() -> Array

常见的 toArray 方法的别名。

find

find(iterator) -> firstElement | undefined

查找第一个使 iterator 返回 true 的元素。detect 方法的简称,应优先使用 find,因为 find 更具有可读性。参数 iterator 是一个函数对象。

findAll

findAll(iterator[, context]) -> Array

获取所有使 iterator 返回 true 的元素。别名为 select

grep

grep(regex[, iterator = Prototype.K[, context]]) -> Array

返回所有和指定的正则表达式匹配的元素。如果指定参数 iterator,则可以对匹配的元素进行相应处理。

inGroupsOf

inGroupsOf(size[, filler = null]) -> [group...]

按照固定的大小对元素进行分组,如果最后一组包含的元素个数小于指定的大小,则使用参数 filler 指定的值填充。

include

include(object) -> Boolean

判断 Enumerable 中是否存在指定的对象,基于 == 操作符进行比较。别名 member

inject

inject(accumulator, iterator[, context]) -> accumulatedValue

根据参数 iterator 中定义的规则来累计值。首次迭代时,参数 accumulator 为初始值,迭代过程中,iterator 将处理过的值存放在 accumulator 中,并作为下次迭代的起始值,迭代完成后,返回处理过的 accumulator。 该方法常用于构建数组、计数数值总和或平均值等。
译注:参数 iterator 是一个函数对象,Prototype 会传递三个参数给该对象, 第一个参数是需要累计的对象,即 accumulator,第二个参数是 Enumerable 中的元素,第三个参数是元素的数字索引。

invoke

invoke(methodName[, arg...]) -> Array

eachcollect 的一种常见应用情形进行优化:需要使用同一个方法并具有一致的参数来处理 Enumerable 中的每一个元素。 返回调用指定方法后的结果集。

map

map(iterator) -> Array

通过 iteratorEnumerable 中的元素进行变换,返回变换后的结果。 collect 方法的一个别称。

max

max([iterator = Prototype.K[, context]]) -> maxValue

返回 Enumerable 中元素的最大值,若指定 iterator,则使用 iterator 对元素进行处理,并返回处理后的最大值。如果 Enumerable 为空,返回 undefined

member

member(object) -> Boolean

判断 Enumerable 中是否存在指定的对象,基于 == 操作符进行比较。别名 include

min

min([iterator = Prototype.K[, context]]) -> minValue

返回 Enumerable 中元素的最小值,若指定 iterator,则使用 iterator 对元素进行处理,并返回处理后的最小值。如果 Enumerable 为空,返回 undefined

partition

partition([iterator = Prototype.K[, context]]) -> [TrueArray, FalseArray]

把元素分为两组:一组为 true,一组为 false。默认情形下使用 Javascript 标准规范判断元素等价的 bool 值,如果指定了 iterator 参数,根据 iterator 定义的函数判断元素等价的 bool 值。

pluck

pluck(propertyName) -> Array

collect 方法的一种常见使用情形的优化: 获取所有元素的同一个属性的值,并返回相应的数组。

reject

reject(iterator[, context]) -> Array

获取所有使 iterator 返回 false 的元素。

select

select(iterator) -> Array

获取所有使 iterator 返回 true 的元素。findAll 方法的别名。

size

size() -> Number

返回 Enumerable 中元素的数目。

sortBy

sortBy(iterator[, context]) -> Array

遍历元素,根据 iterator 返回的值,对 Enumerable 中的元素进行排序。

toArray

toArray() -> Array

Enumerable 表示为 Array。别名 entries

zip

zip(Sequence...[, iterator = Prototype.K]) -> Array

将多个(两个及以上)序列按照顺序配对合并(想像一下拉链拉上的情形)为一个包含一序列元组的数组。 元组由每个原始序列的具有相同索引的元素组合而成。如果指定了可选的 iterator 参数,则元组由 iterator 指定的函数生成。