Prototype源碼淺析 Enumerable部分(二)

來源:互聯網
上載者:User

前面each方法中掉了一個方面沒有說,就是源碼中的$break和$continue。這兩個變數是預定義的,其作用相當於普通迴圈裡面的break和continue語句的作用。出於效率的考慮,在某些操作中並不需要完全遍曆一個集合(不局限於一個數組),所以break和continue還是很必要的。
對於一個迴圈來說,對比下面幾種退出迴圈的方式:
複製代碼 代碼如下:
var array_1 = [1,2,3];
var array_2 = ['a','b','c'];
(function(){
for(var i = 0, len = array_1.length; i < len; i++){
for(var j = 0, len_j = array_1.length; i < len_j; j++){
if('c' === array_2[j]){
break;
}
console.log(array_2[j]);
}
}
})();//a,b,a,b,a,b
(function(){
for(var i = 0, len = array_1.length; i < len; i++){
try{
for(var j = 0, len_j = array_1.length; i < len_j; j++){
if('c' === array_2[j]){
throw new Error();
}
console.log(array_2[j]);
}
}catch(e){
console.log('退出一層迴圈');
}
}
})();//a,b,'退出一層迴圈',a,b,'退出一層迴圈',a,b,'退出一層迴圈'
(function(){
try{
for(var i = 0, len = array_1.length; i < len; i++){
for(var j = 0, len_j = array_1.length; i < len_j; j++){
if('c' === array_2[j]){
throw new Error();
}
console.log(array_2[j]);
}
}
}catch(e){
console.log('退出一層迴圈');
}
})();//a,b,'退出一層迴圈'

  當我們把錯誤捕獲放在相應的迴圈層面時,就可以中斷相應的迴圈。可以實現break和break label的作用(goto)。這樣的一個應用需求就是可以把中斷挪到外部去,恰好符合Enumerable處的需求。

  回到Enumerable上來,由於each(each = function(iterator, context){})方法的本質就是一個迴圈,對於其第一個參數iterator,並不包含迴圈,因此直接調用break語句會報語法錯誤,於是Prototype源碼中採用上面的第二種方法。
複製代碼 代碼如下:
Enumerable.each = function(iterator, context) {
var index = 0;
try{
this._each(function(value){
iterator.call(context, value, index++);
});
}catch(e){
if(e != $break){
throw e;
}
}
return this;
};

  一旦iterator執行中拋出一個$break,那麼迴圈就中斷。如果不是$break,那麼就拋出相應錯誤,程式也穩定點。這裡的$break的定義並沒有特殊要求,可以按照自己的喜好隨便更改,不過意義不大。

Enumerable中的某些方法在一些現代瀏覽器裡面已經實現了(參見chrome原生方法之數組),下面是一張對比圖:


在實現這些方法時,可以借用原生方法,從而提高效率。不過源碼中並沒有借用原生的部分,大概是因為Enumerable除了混入Array部分外,還需要混入其他的對象中。

  看上面的圖示明顯可以看得出,each和map 的重要性,map其實本質還是each,只不過each是依次處理集合的每一項,map是在each的基礎上,還把處理後的結果返回來。在Enumerable內部,map是collect方法的一個別名,另一個別名是select,其內部全部使用的是collect這個名字。

檢測:all | any | include

這三個方法不涉及對原集合的處理,傳回值均是boolean類型。

all : 若 Enumerable 中的元素全部等價於 true,則返回 true,否則返回 false
複製代碼 代碼如下:
function all(iterator, context) {
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
});
return result;
}

  對於all方法來說,裡面的兩個參數都不是必須的,所以,內部提供了一個函數,以代替沒有實參時的iterator,直接返回原值,名字叫做Prototype.K。Prototype.K的定義在庫的開頭,是一個返回參數值的函數Prototype.K = function(x){return x;}。另外,在all方法中,只要有一個項的處理結果為false,整個過程就可以放棄(break)了,於是用到了本文開頭的中斷迴圈的方法。最後的形式就是:
複製代碼 代碼如下:
Prototype.K = function(){};
Enumerable.all = function(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
if (!result) throw $break;
});
return result;
}

  最後返回的result是一個boolean型,偏離一下all,我們改一下result:
複製代碼 代碼如下:
function collect(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator.call(context, value, index));
});
return results;
}

  此時results是一個數組,我們不中斷處理過程,儲存所有的結果並返回,恩,這就是collect方法,或者叫做map方法。


any:若 Enumerable 中的元素有一個或多個等價於 true,則返回 true,否則返回 false,其原理和all差不多,all是發現false就收工,any是發現true就收工。
複製代碼 代碼如下:
function any(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
this.each(function(value, index) {
if (result = !!iterator.call(context, value, index))
throw $break;
});
return result;
}

include:判斷 Enumerable 中是否存在指定的對象,基於 == 操作符進行比較  這個方法有一步最佳化,就是調用了indexOf方法,對於數組來說,indexOf返回-1就不可以知道相應元素不存在了,如果集合沒有indexOf方法,就只能尋找比對了。這裡的尋找和沒有任何演算法,一個個遍曆而已,如果要改寫也容易,不過平時應用不多,因此估計也沒有花這個精力去最佳化這個。所以如果結果為true的時候效率比結果為false的時候要高一些,看運氣了。
複製代碼 代碼如下:
function include(object) {
if (Object.isFunction(this.indexOf))//這個判定函數應該很熟悉了
if (this.indexOf(object) != -1) return true;//有indexOf就直接調用

var found = false;
this.each(function(value) {//這裡的效率問題
if (value == object) {
found = true;
throw $break;
}
});
return found;
}


下面是一組過濾資料的方法:返回單個元素:max | min | detect返回一個數組:grep | findAll | reject | partition 其中max和min並不局限於數位比較,字元的比較一樣可以。max(iterator, context)依舊可以帶有兩個參數,可以先用iterator處理之後再來比較值,這樣的好處就是不必局限於特定的資料類型,比如,對象數組按照一定規則取最大值:
複製代碼 代碼如下:
console.dir([{value : 3},{value : 1},{value : 2}].max(function(item){
return item.value;
}));//3

因此源碼的實現方式可以想象,直接比較的時候,實現方式可以如下:
複製代碼 代碼如下:
function max() {
var result;
this.each(function(value) {
if (result == null || value >= result) //result==null是第一次比較
result = value;
});
return result;
}

擴充之後,value要進一步變為value = (iterator處理後的傳回值):
複製代碼 代碼如下:
function max(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value >= result)
result = value;
});
return result;
}

min的原理也一樣。detect和any的原理和接近,any是找到一個true就返回true,detect是找到一個true就返回滿足true條件的那個值。源碼就不貼了。grep 這個很眼熟啊,一個unix/linux工具,其作用也很眼熟——就是返回所有和指定的Regex匹配的元素。只不過unix/linux只能處理字串,這裡擴充了範圍,但是基本形式還是沒有變。如果集合的每一項都是字串,那麼實現起來回事這樣:
複製代碼 代碼如下:
Enumerable.grep = function(filter) {
if(typeof filter == 'string'){
filter = new RegExp(filter);
}
var results = [];
this.each(function(value,index){
if(value.match(filter)){
results.push(value);
}
})
return results;
};

但是有一現在要處理的集合可能並都是字串,為了達到更廣泛的應用,首先要考慮的就是調用形式。看上面的實現,注意這麼一句:
if(value.match(filter))
其中value是個字串,match是String的方法,現在要擴充所支援的類型,要麼給每一個value都加上match方法,要麼轉換形式。顯然第一種巨響太大,作者轉換了思路:
if (filter.match(value))
這麼一來,不論value為何值,只要filter有對應的match方法即可,上面對於RegExp對象,是沒有match方法的,於是在源碼中,作者擴充了RegExp對象:
RegExp.prototype.match = RegExp.prototype.test;
注意上面的match和String的match有本質區別。這麼一來,如果value是對象,我們的filter只需要提供相應的檢測對象的match方法即可。於是就有:
複製代碼 代碼如下:
function grep(filter, iterator, context) {
iterator = iterator || Prototype.K;
var results = [];

if (Object.isString(filter))
filter = new RegExp(RegExp.escape(filter));

this.each(function(value, index) {
if (filter.match(value))//原生filter是沒有match方法的。
results.push(iterator.call(context, value, index));
});
return results;
}

  對於匹配的結果,可以處理之後再返回,這就是iterator參數的作用。不同於max方法,grep是進行主要操作時候再用iterator來處理結果,max是用iterator處理來源資料之後再來進行主要操作。因為grep中的filter代替了max中iterator的作用。至於findAll,是grep的加強版,看過grep,findAll就很簡單了。reject就是findAll的雙子版本,作用正好相反。partition就是findAll + reject,組合親子版本。轉載請註明來自小西山子【http://www.cnblogs.com/xesam/】

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.