jQuery源碼閱讀(十一)---each、map、grep、merge、makeArray、inArray解讀

這兩天都在看jQuery源碼中的靜態方法,上一篇博客介紹了類型檢測這一類靜態方法,這次主要對後面幾個常用的方法進行分析。主要包括:

each: function(){}    
map: function(){}     
grep: function()
merge: function(){}
makeArray: function(){}
inArray: function(){}

each、map、grep

each、map、grep這三個函數是比較相似的,所以想放在一起來分析。
首先先要了解三個函數的功能:

var arr = [2,3,5,9];
var res = $.each(arr, function(i, item){
    console.log(i + ' ' + item);
})

這裏寫圖片描述

var obj = [3,5,7,9,2, 4, 6, 8];
res = $.map(obj, function( item, i){
    if(item % 2 == 0)
    {
        return item;
    }
});

這裏寫圖片描述

var arr = [4,2,8,6,4,3,0,6];
var res = $.grep(arr, function(item, i){
    return item > 4;               //過濾條件,返回一個數組
    });
var res1 = $.grep(arr, function(item, i){
    return item > 4;               //過濾條件,返回一個數組
    }, true);

這裏寫圖片描述

上面是三種方法的功能測試,可以看到,each主要是對數組(對象)中的每個元素進行遍歷或者一系列操作;map和grep的話其實都可以認爲是過濾函數,按照一定的規則去過濾和映射,不同的是,map可以對 對象來操作,但是grep只能過濾數組。

下來分析一下源碼。
each源碼:

function( object, callback, args ) {
    var name, i = 0,
        length = object.length,          //如果是對象,沒有length屬性
        //判斷是否爲對象或者函數,函數的話有length屬性,所以需要另外判斷
        isObj = length === undefined || jQuery.isFunction( object );    

    //args是要傳入到callback裏面的參數,注意這個args需要傳數組的形式,因爲下面利用的是callback.apply。
    if ( args ) {
        //整體思路是分別對對象和數組(或僞數組)處理;
        //object爲對象時,用for ... in 來遍歷
        if ( isObj ) {
            for ( name in object ) {
                if ( callback.apply( object[ name ], args ) === false ) {
                    break;
                }
            }
        } else {
            //object爲數組(僞數組)時,直接利用for循環。
            //對每一個元素分別調callback方法
            for ( ; i < length; ) {
                if ( callback.apply( object[ i++ ], args ) === false ) {
                    break;
                }
            }
        }
    } else {
        //無參數情況與上面情況類似,默認的參數爲元素索引index和數組元素item
        if ( isObj ) {
            for ( name in object ) {
                if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
                    break;
                }
            }
        } else {
            for ( ; i < length; ) {
                if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
                    break;
                }
            }
        }
    }

    return object;
}

grep源碼:

function( elems, callback, inv ) {
    //ret空數組是用來保存過濾元素的,最後作爲結果返回
    var ret = [], retVal;
    inv = !!inv;   //inv表示是否反轉結果;!!inv是用來將inv轉換成Boolean類型的。默認不傳改參數,就是false

    for ( var i = 0, length = elems.length; i < length; i++ ) {
        //對數組中每個元素執行過濾規則(即callback函數),並將返回的結果轉換爲Boolean值
        retVal = !!callback( elems[ i ], i );
        //若有返回值,retVal爲true; 默認情況下inv爲false,這時將當前元素Push進ret數組中,保存起來。
        //同理,若inv設爲true,表示將未滿足過濾規則的元素push進結果數組裏。
        if ( inv !== retVal ) {
            ret.push( elems[ i ] );
        }
    }
    return ret;
}

map源碼:

function( elems, callback, arg ) {
    var value, key, ret = [],
        i = 0,
        length = elems.length,

        //判斷elems是否爲數組(或僞數組)
        //jQuery對象算是僞數組
        isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;

    // 當elems爲數組時,for循環遍歷每個元素,並對每個元素執行callback函數,返回結果加到ret數組裏
    if ( isArray ) {
        for ( ; i < length; i++ ) {
            value = callback( elems[ i ], i, arg );

            if ( value != null ) {
                ret[ ret.length ] = value;
            }
        }
    } else {
        //當elems爲對象時,利用for...in 遍歷每個元素,並對每個元素調callback函數
        for ( key in elems ) {
            value = callback( elems[ key ], key, arg );

            if ( value != null ) {
                ret[ ret.length ] = value;
            }
        }
    }

    //最後是對結果的扁平化
    return ret.concat.apply( [], ret );
}

這裏主要對map函數最後 扁平化進行分析。這是爲了防止數組嵌套的情況。假設這麼一種情況:

var obj = [ [1,2,3], [4,5,6]];
res = $.map(obj, function( item, i){
        return item;
    });

這裏寫圖片描述
可以看到最後得到的結果數組裏,中間並沒有嵌入數組。這是如何解決的呢?
在jQuery源碼中,利用了apply和數組的原生方法concat。

ret.concat.apply( [], ret );

concat作用是將數組進行連接,並返回一個新的數組。利用apply,首先將空數組和ret數組中第一個元素進行連接,返回一個新的數組,然後再將新的數組和ret中第二個元素連接,以此類推。最終得到一個新數組。

makeArray、inArray、merge

這三個方法都相當於是數組(類數組)上的操作。

makeArray:將傳入的參數轉換成數組
inArray: 判斷某個元素是否在數組中
merge: 將兩個數組合併成數組

makeArray源碼:

function( array, results ) {
    var ret = results || [];

    if ( array != null ) {

        var type = jQuery.type( array );

        //array爲對象,字符串,函數,正則,Window對象,都認爲是一個整體,直接將其加入到ret數組中
        if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
            push.call( ret, array );
        } else {
            //array爲數組或僞數組,調用jQuery.merge方法,這在[之前的博客中](http://blog.csdn.net/u010046318/article/details/74223908)已講過
            jQuery.merge( ret, array );
        }
    }

    return ret;
}

inArray源碼:

function( elem, array, i ) {
    var len;

    //不對對象和字符串起作用
    if ( array ) {
        //調原生的Array.prototype.indexOf方法
        if ( indexOf ) {
            return indexOf.call( array, elem, i );
        }
        //若瀏覽器不支持上面方法,下面手動實現,遍歷一遍,找到數組中元素與elem相等的元素,並返回index。沒找到,就返回-1.
        len = array.length;
        i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
        for ( ; i < len; i++ ) {

            if ( i in array && array[ i ] === elem ) {
                return i;
            }
        }
    }

    return -1;
}

到這裏,jQuery靜態方法基本上已經整理完了,其他的幾個方法不是很常用的,所以大致過了一遍。下來的部分應該就是jQuery各個模塊的實現了,比如回調對象,延遲對象,隊列,異步隊列,Sizzle等等,這些底層的一些東西也用到了上面提到的靜態方法。所以後面看的過程中,一方面學習新的思想和技巧,另一方面回顧之前總結的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章