Prototype源碼淺析——Enumerable部分(三)

來源:互聯網
上載者:User

現在來看Enumerable剩下的方法

toArray | size | inspect

inject | invoke | sortBy | eachSlice | inGroupsOf | plunk | zip

 

前面說過map的原理,不管原來的集合是什麼,調用map之後返回的結果就是一個數組,其中數組的每一項都是經過interator處理了的,如果不提供interator那麼預設使用Prototype.K,此時的作用很明顯,返回的結果就是原來集合的數組形式。原來的集合中length屬性為多少,返回結果數組的length就是多少。

這個特殊情況被作為一個方法獨立出來,叫做toArray:

  function toArray() {
return this.map();
}

另一個size方法是返回上面那個數組的長度:

  function size() {
return this.length;
}

至於inspect,前面在Object和Strng部分見過好幾次了,這裡調用Array的inspect方法[Array部分後面分析,不過inspect這個方法太熟悉了,也沒必要說了]。

  function inspect() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}

另外,對於集合來說,另一個操作是分組。Enumerable中對應的方法是eachSlice 和 inGroupsOf。

先拋開源碼,單獨來實現這個方法,需要的一個參數是每一個分組的長度(叫做number),如果最後一組長度不夠,就保留最後一組的實際長度。

具體實現步驟:

第一、檢測number的值,如果number小於1,那麼肯定是非法字元,直接返回原來的集合。

第二、將所操作集合轉變為數組A,轉變方法就是在原來的集合上面調toArray方法。

第三、數組A分組有原生的方法slice,迴圈調用即可。

按照上面的步驟,可得到下面的實現:

    Enumerable.eachSlice = function(number){
if(number < 1){ //第一步
return this;
}
var array = this.toArray();//第二步
var index = 0;
var results = [];
while(index < array.length){//第三步
results.push(array.slice(index,index + number));
index += number;
}
return results;
};

對應到具體的源碼實現中,index += number;這一步被挪到while的條件中,因此,為了保證迴圈從0開始,index的初始值被設為-number;另外,和其他的方法一樣,eachSlice也提供了iterator, context兩個參數,作用依舊不變,所以最後的結果變成:

  function eachSlice(number, iterator, context) {
var index = -number, slices = [], array = this.toArray();
if (number < 1) return array;
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.collect(iterator, context); //collect就是map
}

例子:

console.log([1,2,3,4,5].eachSlice(2));//[[1,2],[3,4],[5,6]]

至於inGroupsOf方法,則是對eachSlice的一個補充而已。eachSlice最後一組長度不夠,就保留最後一組的實際長度,在inGroupsOf最後一組長度不夠,會用指定的填充符填充(預設填充為null)。因此只要提供iterator函數就可以了:

    Enumerable.inGroupsOf = function(number, fillWith) {
fillWith = typeof fillWith == 'undefined' ? null : fillWith;//源碼中這裡使用的是Object.isUndefined
return this.eachSlice(number, function(slice) {
while(slice.length < number){
slice.push(fillWith);
}
return slice;
});
}

下面看inject,先看手冊說明:

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

根據參數 iterator 中定義的規則來累計值。首次迭代時,參數 accumulator 為初始值,迭代過程中,iterator 將處理過的值存放在 accumulator 中,並作為下次迭代的起始值,迭代完成後,返回處理過的 accumulator。

這個操作其實前面也遇到過,可以單獨用each來實現,看一個數組求和的例子:

    console.dir([1,2,3,4].inject(0,function(sum,n){
return sum + n;
}));//10

如果換做each實現,就是:

    var sum = 0;
[1,2,3,4].each(function(value){
sum += value;
})
console.dir(sum);//10

 

對比上面的實現和源碼中的實現,上面的實現中有一個缺陷:sum全域變數,這是不合理的。

所以變形上面的形式,拋棄那個全域變數sum,由於each方法是固定死的,沒有辦法再改變,所以我們在外層再封裝一個方法,並將疊加部分填進去:

    function fn(accumulator,interator,context){
[1,2,3,4].each(function(value,index){
accumulator = interator.call(context,accumulator,value,index);
})
return accumulator;
}
console.dir(fn(0,function(accumulator,value,index){
accumulator += value;
return accumulator;
}));//10

 

看上面的實現,需要注意的一點是,fn中第一個傳入的是一個參考型別的變數,由於這一個實現方式:

accumulator = interator.call(context,accumulator,value,index);

那麼最後返回的結果是同一個變數,是對最初傳入變數的一個引用,這一點是出於效能和效率的考慮,不過有時候可能導致問題,需警惕。

 

接下來是invoke方法,這個方法和each(map)的作用基本一致,唯一的區別是each(map)執行的是外部提供的一個方法,而invoke執行的是集合對象自身本來就存在的方法。因此,invoke的參數有且只需要有一個,就是集合對象的方法名。舉個例子,我們將一個數字數組的每一項都轉化為字串:

對比兩種實現:

    var array_1 = [1,2,3,4].map(function(value){
return value.toString();
});
console.log('array_1:',array_1);//["1", "2", "3", "4"]
var array_2 = [1,2,3,4].invoke('toString');
console.log('array_2:',array_2);//["1", "2", "3", "4"]

由於少了一次閉包的消耗,因此invoke在效率上稍高,而且形式也簡潔不少。

具體實現:

  function invoke(method) {
//var args = $A(arguments).slice(1); //源碼實現
var args = Array.prototype.slice.call(arguments,1);
return this.map(function(value) {
return value[method].apply(value, args);
});
}

 

plunk方法比較簡單,擷取所有元素的同一個屬性的值,並返回相應的數組。這個方法顯然是針對普通對象來的,數組的話沒有什麼屬性好取的:

  function pluck(property) {
var results = [];
this.each(function(value) {
results.push(value[property]);
});
return results;
}

 

源碼好理解,給個例子就行:

['hello', 'world', 'my', 'is', 'xesam'].pluck('length')// [5, 5, 2, 3, 5]

 

另一個在其他指令碼裡面常見的方法是zip,這個方法不是很好理解:

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

將多個(兩個及以上)序列按照順序配對合并(想像一下拉鏈拉上的情形)為一個包含一序列元組的數組。 元組由每個原始序列的具有相同索引的元素組合而成。如果指定了可選的 iterator 參數,則元組由 iterator 指定的函數產生。 
我們先不考慮iterator ,來第一個實現:

    function zip(){
var array_1 = [1,2,3];
var array_2 = ['a','b','c'];
var array_3 = ['x','y','z'];
var result = [array_1].concat([array_2,array_3]);//[[1,2,3],['a','b','c'],['x','y','z']]
return array_1.map(function(value,index){
return result.pluck(index);
})
}
console.log(zip());//[[1,'a','x'],[2,'b','y'],[3,'c','z']]

改為Enumerable方法的形式就是:

    function zip(){
var result = [this].concat(Array.prototype.slice.call(arguments,1));
return this.map(function(value,index){
return result.pluck(index);
})
}
console.log([1,2,3].zip(['a','b','c'],['x','y','z']));////[[1,'a','x'],[2,'b','y'],[3,'c','z']]

加上interator處理之後就是:

    function zip(){
var args = Array.prototype.slice.call(arguments,0);
var interator = function(x){ return x;} //Prototype.K
if(args[args.length - 1].constructor == Function){
interator = args.pop();
}
var result = [this].concat(Array.prototype.slice.call(arguments,1));
return this.map(function(value,index){
return interator.call(result.pluck(index));
})
}

 

 

 

轉載請註明來自小西山子【http://www.cnblogs.com/xesam/】
本文地址:http://www.cnblogs.com/xesam/archive/2012/01/19/2326142.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.