Enumerable
Enumerable
为枚举提供了大量有用的方法,从而让枚举对象可以像值的集合一样使用。它是 Prototype 的奠基石。
我们喜欢把 Enumerable
称之为一个 module:它具有一系列相关的方法,创建它的目的并不是为了独立使用,而是为了
混入(mixin):组合到其它对象中。术语“module”是从 Ruby 中借用过来的,用在这里非常贴切,因为 Enumerable
试图模仿 Ruby 中 Enumerable 模块的部分功能。
Prototype 中的许多对象都混入了 Enumerable
。Array 和
Hash 是最明显的例子,但不仅于此,在一些不引人注目的地方,你也能找到它,比如
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
中找到的别名:
map
和 collect 是相同的。- find 是首选的,也可以使用
detect
。 - findAll 和
select
是相同的。 - include 和
member
是相同的。 entries
和 toArray 是相同的。
如何有效的使用?
在使用 Enumerable
的过程中,因不熟悉相应的 API,初学者经常会写出一些执行效率低下的代码。在一些常见的情形中,
使用其中一种方式其执行效率明显优于其它方式(通常代码也更具有可读性),这主要表现在以下两个方面:
collect
、invoke
、pluck
和 each
,认真考虑一下这种情形:
在操作 Enumerable
中的所有元素时,初学者往往趋向于使用 each,
而针对 Enumerable
中的每个元素进行相同的处理并获取一个值的集合时,则使用
collect
。通常情况下,这无可非议,但是,在一些特殊的情形下,使用下述方式将会使程序变得更为简捷、优雅和有效:
reject
和 findAll
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 中 Array
、Hash
或 ObjectRange
对象的源代码。它们都包含了一个 _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
通过 iterator
对 Enumerable
中的元素进行变换,返回变换后的结果集。该方法有一个别名
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
中的元素进行分组,最后一组元素的个数可能小于指定的个数。
find
find(iterator) -> firstElement | undefined
查找第一个使 iterator
返回 true
的元素。detect
方法的简称,应优先使用 find
,因为 find
更具有可读性。参数 iterator
是一个函数对象。
grep
grep(regex[, iterator = Prototype.K[, context]]) -> Array
返回所有和指定的正则表达式匹配的元素。如果指定参数 iterator
,则可以对匹配的元素进行相应处理。
inGroupsOf
inGroupsOf(size[, filler = null]) -> [group...]
按照固定的大小对元素进行分组,如果最后一组包含的元素个数小于指定的大小,则使用参数 filler
指定的值填充。
inject
inject(accumulator, iterator[, context]) -> accumulatedValue
根据参数 iterator
中定义的规则来累计值。首次迭代时,参数 accumulator
为初始值,迭代过程中,iterator
将处理过的值存放在 accumulator
中,并作为下次迭代的起始值,迭代完成后,返回处理过的 accumulator
。
该方法常用于构建数组、计数数值总和或平均值等。
译注:参数 iterator
是一个函数对象,Prototype 会传递三个参数给该对象,
第一个参数是需要累计的对象,即 accumulator
,第二个参数是 Enumerable
中的元素,第三个参数是元素的数字索引。
max
max([iterator = Prototype.K[, context]]) -> maxValue
返回 Enumerable
中元素的最大值,若指定 iterator
,则使用 iterator
对元素进行处理,并返回处理后的最大值。如果 Enumerable
为空,返回 undefined
。
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 值。
reject
reject(iterator[, context]) -> Array
获取所有使 iterator
返回 false
的元素。
size
size() -> Number
返回 Enumerable
中元素的数目。
sortBy
sortBy(iterator[, context]) -> Array
遍历元素,根据 iterator
返回的值,对 Enumerable
中的元素进行排序。
zip
zip(Sequence...[, iterator = Prototype.K]) -> Array
将多个(两个及以上)序列按照顺序配对合并(想像一下拉链拉上的情形)为一个包含一序列元组的数组。
元组由每个原始序列的具有相同索引的元素组合而成。如果指定了可选的 iterator
参数,则元组由
iterator
指定的函数生成。