Enumerable

笔者的理解是Enumerable似乎是一个接口集,有些像C#中的抽象类(Abstract),所以这部分只能按照原文翻译了,还请高人指点迷津啊.

Enumerable提供了大量的有用的用于列举(enumeration)的方法,他代表着一系列的值的集合,是Prototype框架的基础.

Enumerable可以称为一个"模块",提供统一的一系列"非独立使用(intended not for independant use)"而是"混合使用(mixin)"的方法,他需要其他的"适应"他的对象协助他一起使用,这个模块的构想来自于相当"适应"的Ruby(别扭),所以Enumerable想模仿Ruby的相当一部分的同名对象(namesake).

在prototype框架有些对象已经和Enumaerable混在一起了,常见的有Hash,Array,其次ObjectRange,DOM集合,Ajax有关的对象也和Enumerable有些相似之处

别名:完全是为了方便你的使用

在最后的实现中可以看见
map==collect
find==detect
select==findAll
member==include
entries=toArray

有效的使用

初学者由于缺乏Enumerable API的认识,会写很多部分代码(sub-par code),追求很高的性能(performance-wise).其实有很多的方法可以获得更好的性能和更好的可读性,下面列举主要的两点:

1.collect,invoke,pluck,each考虑使用情况

初学者总是趋向在枚举操作所有元素时使用all,在对每一个元素应用方法时使用collect,下面的方法是在通常情况下更合适的方法,而且在一些特殊的情况下,会应用的更加精确,更加文雅,获得更好的性能.

2.reject,findAll区别于partition

findAll/select是按照符合一个给定的谓词紧缩所有元素,而reject是按照不符合给定的谓词紧缩所有元素.在一些特殊的情况下你可能需要用两中方法,这时候你可以使用partition

"混合"Enumerable到你的对象

要实现Enumerable,最基本的是要实现_each(),然后你可以在你的对象实现时重载你的方法

var YourObject=Class.create();
Object.extend(YourObject.prototype,Enumerable);//扩展Enumerable
Object.extend(YourObject.prototype,{
initialize:function(){//实现你的构造函数(任意参数)
...
},
_each:function(iterator){
//Your iteration code,invoking iterator at every turn
//迭代代码,在每次循环是回调用这个迭代器(iterator)
},
//Your other method shere,including Enumerable overrides
//其他方法的实现,可以重载Enumerable的方法
});

使用方法
var obj=new YourObject();
obj.pluck('property');
obj.invoke('method');
obj.size();
//...

all([iterator=Prototype.K])->Boolean:判断元素是否至少有一个元素迭代等于false

[].all();
//->true 空数组没有元素可以等于false,所以返回true

$(1,5).all();
//->true 所有元素都不等于false,返回true

$(0,3).all();
//->false 0认为等于false

[3,5,7].all(function(n){return n>6;});
//->false 可以自写一个函数判断是否符合要求,该函数可以为两个参数(value,index)(同each)

$H([{name:'Hafeyang',isFemale:false}]).all(function(pair){return pair.value;})
//->false  isFemale/false

any([iterator=Prototype.K])->Boolean判断元素是否至少有一个元素迭代等于true

[].any();
//->false 没有元素等于true

[1,4].any(function(n){return n%2==1;})
//->false 1%2==1

$R(0,2).any();
//->true 1!=false

$H({opt1:false,opt2:0,opt3:null,opt4:"H"}).any(function(pair){return pair.value});
//opt4/"H"==true


collect/map:collect(iterator)->Array

['Hitch',"Hiker's",'Guide','To','The','Galaxy'].collect(function(s){
    returns.charAt(0).toUpperCase();
}).join('')
//->'HHGTTG'

$R(1,5).collect(function(n){
return n*n;
})
//->[1,4,9,16,25]

为了获取更好的性能,在获取属性时推荐使用pluck,调用方法是使用invoke,这两中方法的速度都比collect/map快

detect()/find():find(iterator)->firstElement|undefined,判断是否有元素等于true,若有返回头一个元素.无则返回undefined

function isPrime(n){
    if(2>n) return false;
    if(0==n%2) return(2==n);
    for(var index=3; n/index>index; index+=2)
        if(0==n%index)return false;
return true;
}

$R(10,15).find(isPrime)
//->11

['hello','world','this','is','nice'].find(function(s){
returns.length<=3;
})
//->'is'

each(iterator)->Enumerable:Enumerable的基础 ,iterator有两个参数(value.index)

["First","Second","Third"].each(function(str,index){alert(index+":"+str);})

在each方法中可以抛出两种异常,$continue,$break
var result=[];
$R(1,10).each(function(n){
     if(0==n%2)
         throw $continue;
     if(n>6)
         throw $break;
result.push(n);
});
//result->[1,3,5]

each与_each的区别

从性能上来讲,invoke的性能优于each

entries()/toArray():转化为数组

$(0,2).toArray();//->[0,1,2]

select()/findAll():findAll(iterator)->Array,返回所有迭代结果等于true的元素

$R(1,10).findAll(function(n){return0==n%2;})
//->[2,4,6,8,10]
['hello','world','this','is','nice'].findAll(function(s){
returns.length>=5;
})
//->['hello','world']

grep():grep(regex[,iterator=Prototype.K])->Array,返回正则表达式返回true的元素

//获得字符串中有连续两字母相同的字符串
['hello','world','this','is','cool'].grep(/(.)\1/)
//->['hello','cool']

//获得以0或5结尾的数字
$R(1,30).grep(/[05]$/)
//->[5,10,15,20,25,30]

//获得以0或5结尾的数字  并都减1
$R(1,30).grep(/[05]$/,function(n){returnn-1;})
//->[4,9,14,19,24,29]

size():返回元素个数,等于toArray().length

sortBy():sortBy(iterator)->Array,按迭代结果排序.也可以直接调用sort()方法,只是这样调用的排序规则是 运算符>

['hello','world','this','is','nice'].sortBy(function(s){returns.length;})
//->'is','this','nice','hello','world']

['hello','world','this','is','cool'].sortBy(function(s){
var md=s.match(/[aeiouy]/g);//match()返回一个包含在字符串中的所有匹配的数组
return null==md?0:md.length;
})
//->['world','this','is','hello','cool'](根据匹配次数排序)

include()/member():include(object)->Boolean,使用的是"=="并非严格的"==="

$R(1,15).include(10)//->true
['hello','world'].include('HELLO')//->false  'H'!='h'
[1,2,'3','4','5'].include(3)
//->true(==ignores actual type)忽略实际的类型  只要==

inject():inject(accumulator,iterator)->accumulatedValue,accumulator:累积结果存放的数组,与accumulatedValue相同;iterator含三个参数(accumulateArray,value,index).该方法避免了复制大容量的数组

$R(1,10).inject(0,function(acc,n){return acc+n;})//->55(1+2+3...+10=55)

$R(2,5).inject(1,function(acc,n){return acc* n;})//->120( 5!=120 )

['hello','world','this','is','nice'].inject([],function(array,value,index){
if(0==index%2)
array.push(value);
return array;
})
//->['hello','this','nice']

//Note how we can use references(see next section):
var array1=[];
var array2=[1,2,3].inject(array1,function(array,value){
array.push(value*value);
return array;
});
array2//->[1,4,9]
array1//->[1,4,9]
array2.push(16);
array1//->[1,4,9,16]

invoke():invoke(methodName[,arg...])->Array  性能很高的调用方法,返回每次迭代的结果的数组

['hello','world','cool!'].invoke('toUpperCase')//->['HELLO','WORLD','COOL!']

['hello','world','cool!'].invoke('substring',0,3)//=>['hel','wor','coo']

$('navBar','adsBar','footer').invoke('hide')

max()/min():max([iterator=Prototype.K])->maxValue,返回每次迭代结果中最大/最小的元素

$R(1,10).max()
//->10

['hello','world','gizmo'].max()
//->'world'

function Person(name,age){
this.name=name;
this.age=age;
}
var john=new Person('John',20);
var mark=new Person('Mark',35);
var daisy=new Person('Daisy',22);
[john,mark,daisy].max(function(person){
return person.age;
})
//->35

partition():partition([iterator=Prototype.K])->[TrueArray,FalseArray],将每次迭代返回的结果数组按等于true/false分开成两个数组,并返回

['hello',null,42,false,true,,17].partition()
//->[['hello',42,true,17],[null,false,undefined]]

$R(1,10).partition(function(n){
return 0==n%2;
})
//->[[2,4,6,8,10],[1,3,5,7,9]]

pluck():pluck(propertyName)->Array,获取每个元素的同一属性值的数组

['hello','world','this','is','nice'].pluck('length')
//->[5,5,4,3,4]

document.getElementsByClassName('superfluous').pluck('tagName').sort().uniq()
//获取所有的class="superfluous"的结点的tagName属性值并排序去掉相同tagName得到tagName[]

reject():reject(iterator)->Array,驱除所有迭代结果为false的元素,返回去除后的数组


$R(1,10).reject(function(n){return0==n%2;})
//->[1,3,5,7,9]

['hello','world','this','is','nice'].reject(function(s){
     returns.length>=5;
})
//->['this','is','nice']

zip():zip(Sequence...[,iterator=Prototype.K])->Array,Sequence也是一个数组,该函数对Sequence数组进行排序,然后与源数组的对应元素按迭代方法行成新的元素,组成新的数组返回. Sequence可以是多个数组,如果Sequence的长度与源数组不同,则迭代次数为较小数组元素个数.iterator的参数是对应元素组成的数组

varfirstNames=['Justin','Mislav','Tobie','Christophe'];
varlastNames=['Palmer','Marohni#','Langel','Porteneuve'];
firstNames.zip(lastNames)
//->[['Justin','Palmer'],['Mislav','Marohni#'],['Tobie','Langel'],['Christophe','Porteneuve']]
//没有iterator,默认是将对应元素组成数组

firstNames.zip(lastNames,function(a){return a.join(' ');})
//->['Justin Palmer','Mislav Marohni#','Tobie Langel','Christophe Porteneuve']
//itorator将数组连接成字符串

var cities=['Memphis','Zagreb','Montreal','Paris'];
firstNames.zip(lastNames,cities,function(p){
return p[0]+' '+p[1]+','+p[2];
})
//->['Justin Palmer,Memphis','Mislav Marohni#,Zagreb','Tobie Langel,Montreal','Christophe Porteneuve,Paris']

firstNames.zip($R(1,100),function(a){returna.reverse().join('.');})
//->['1.Justin','2.Mislav','3.Tobie','4.Christophe']
//去最小长度迭代

Enumerable的实现,当你了解上面的接口之后,Enumerable的实现就显的很简单,下面贴出了实现

//Enumerable或许让很多人迷惑这里贴出其实现

var Enumerable = {
each: function(iterator) {/*此处并非实现了each方法,倒是像定义了这么个接口,等着你去实现,下面的"方法"似乎既是接口,又是实现*/
var index = 0;
try {
this._each(function(value) {
iterator(value, index++);/*iterator重复的调用_each()方法,所以可以自定义实现_each()方法实现自己的each()方法*/
});
} catch (e) {
if (e != $break) throw e;
}
return this;
},

eachSlice: function(number, iterator) { //Enumerable可以转化为Array
var index = -number, slices = [], array = this.toArray();
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.map(iterator);
},

all: function(iterator) {/*定义all接口返回符合要求的全部元素*/
var result = true;
this.each(function(value, index) {
result = result && !!(iterator || Prototype.K)(value, index); /*Prototype.K: function(x) { return x } 注意(iterator || Prototype.K)即默认的使用的iterator为Prototype.K*/
if (!result) throw $break;
});
return result;
},

any: function(iterator) { /*定义any接口返回符合要求的元素*/
var result = false;
this.each(function(value, index) {
if (result = !!(iterator || Prototype.K)(value, index))
throw $break;
});
return result;
},

collect: function(iterator) {/*collect接口实现筛选*/
var results = [];
this.each(function(value, index) {
results.push((iterator || Prototype.K)(value, index));
});
return results;
},

detect: function(iterator) {/*detect接口实现检测*/
var result;
this.each(function(value, index) {
if (iterator(value, index)) {
result = value;
throw $break;
}
});
return result;
},

findAll: function(iterator) {/*findAll接口实现查找*/
var results = [];
this.each(function(value, index) {
if (iterator(value, index))
results.push(value);
});
return results;
},

grep: function(pattern, iterator) {/*grep用正则表达式查找*/
var results = [];
this.each(function(value, index) {
var stringValue = value.toString();
if (stringValue.match(pattern))
results.push((iterator || Prototype.K)(value, index));
})
return results;
},

include: function(object) {/*inlcde接口实现是否包含元素*/
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
return found;
},

inGroupsOf: function(number, fillWith) {//??
fillWith = fillWith === undefined ? null : fillWith;
return this.eachSlice(number, function(slice) {
while(slice.length < number) slice.push(fillWith);
return slice;
});
},

inject: function(memo, iterator) {//注入
this.each(function(value, index) {
memo = iterator(memo, value, index);
});
return memo;
},

invoke: function(method) {//对所有元素调用method方法
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);//Prototype最喜欢用的apply方法,参见Function.html
});
},

max: function(iterator) {//实现查找最大元素
var result;
this.each(function(value, index) {
value = (iterator || Prototype.K)(value, index);
if (result == undefined || value >= result)//默认的是用< > 判断
result = value;
});
return result;
},

min: function(iterator) {//实现查找最小元素
var result;
this.each(function(value, index) {
value = (iterator || Prototype.K)(value, index);
if (result == undefined || value < result)
result = value;
});
return result;
},

partition: function(iterator) {//??
var trues = [], falses = [];
this.each(function(value, index) {
((iterator || Prototype.K)(value, index) ?
trues : falses).push(value);
});
return [trues, falses];
},

pluck: function(property) {//查找属性
var results = [];
this.each(function(value, index) {
results.push(value[property]);
});
return results;
},

reject: function(iterator) {//排除元素
var results = [];
this.each(function(value, index) {
if (!iterator(value, index))
results.push(value);
});
return results;
},

sortBy: function(iterator) {//排序接口
return this.map(function(value, index) {
return {value: value, criteria: iterator(value, index)};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
},

toArray: function() {//转化成数组
return this.map();
},

zip: function() {//??
var iterator = Prototype.K, args = $A(arguments);
if (typeof args.last() == 'function')
iterator = args.pop();

var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
return iterator(collections.pluck(index));
});
},

size: function() {//大小
return this.toArray().length;
},

inspect: function() {//自然不能错过inspect()
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
}

Object.extend(Enumerable, {//将事件绑定,多出了一大堆Aliases(别名)
map: Enumerable.collect,
find: Enumerable.detect,
select: Enumerable.findAll,
member: Enumerable.include,
entries: Enumerable.toArray
});