JavaScript專題之jQuery通用遍歷方法each的實現

JavaScript 專題系列第十一篇,講解 jQuery 通用遍歷方法 each 的實現git

each介紹

jQuery 的 each 方法,做爲一個通用遍歷方法,可用於遍歷對象和數組。github

語法爲:數組

jQuery.each(object, [callback])複製代碼

回調函數擁有兩個參數:第一個爲對象的成員或數組的索引,第二個爲對應變量或內容。app

// 遍歷數組
$.each( [0,1,2], function(i, n){
    console.log( "Item #" + i + ": " + n );
});

// Item #0: 0
// Item #1: 1
// Item #2: 2複製代碼
// 遍歷對象
$.each({ name: "John", lang: "JS" }, function(i, n) {
    console.log("Name: " + i + ", Value: " + n);
});
// Name: name, Value: John
// Name: lang, Value: JS複製代碼

退出循環

儘管 ES5 提供了 forEach 方法,可是 forEach 沒有辦法停止或者跳出 forEach 循環,除了拋出一個異常。可是對於 jQuery 的 each 函數,若是須要退出 each 循環可以使回調函數返回 false,其它返回值將被忽略。函數

$.each( [0, 1, 2, 3, 4, 5], function(i, n){
    if (i > 2) return false;
    console.log( "Item #" + i + ": " + n );
});

// Item #0: 0
// Item #1: 1
// Item #2: 2複製代碼

初版

那麼咱們該怎麼實現這樣一個 each 方法呢?性能

首先,咱們確定要根據參數的類型進行判斷,若是是數組,就調用 for 循環,若是是對象,就使用 for in 循環,有一個例外是類數組對象,對於類數組對象,咱們依然能夠使用 for 循環。ui

更多關於類數組對象的知識,咱們能夠查看《JavaScript專題之類數組對象與arguments》this

那麼又該如何判斷類數組對象和數組呢?實際上,咱們在《JavaScript專題之類型判斷(下)》就講過jQuery 數組和類數組對象判斷函數 isArrayLike 的實現。spa

因此,咱們能夠輕鬆寫出初版:prototype

// 初版
function each(obj, callback) {
    var length, i = 0;

    if ( isArrayLike(obj) ) {
        length = obj.length;
        for ( ; i < length; i++ ) {
            callback(i, obj[i])
        }
    } else {
        for ( i in obj ) {
            callback(i, obj[i])
        }
    }

    return obj;
}複製代碼

停止循環

如今已經能夠遍歷對象和數組了,可是依然有一個效果沒有實現,就是停止循環,按照 jQuery each 的實現,當回調函數返回 false 的時候,咱們就停止循環。這個實現起來也很簡單:

咱們只用把:

callback(i, obj[i])複製代碼

替換成:

if (callback(i, obj[i]) === false) {
    break;
}複製代碼

輕鬆實現停止循環的功能。

this

咱們在實際的開發中,咱們有時會在 callback 函數中用到 this,先舉個不怎麼恰當的例子:

// 咱們給每一個人添加一個 age 屬性,age 的值爲 18 + index
var person = [
    {name: 'kevin'},
    {name: 'daisy'}
]
$.each(person, function(index, item){
    this.age = 18 + index;
})

console.log(person)複製代碼

這個時候,咱們就但願 this 能指向當前遍歷的元素,而後給每一個元素添加 age 屬性。

指定 this,咱們能夠使用 call 或者 apply,其實也很簡單:

咱們把:

if (callback(i, obj[i]) === false) {
    break;
}複製代碼

替換成:

if (callback.call(obj[i], i, obj[i]) === false) {
    break;
}複製代碼

關於 this,咱們再舉個經常使用的例子:

$.each($("p"), function(){
   $(this).hover(function(){ ... });
})複製代碼

雖然咱們常常會這樣寫:

$("p").each(function(){
    $(this).hover(function(){ ... });
})複製代碼

可是由於 $("p").each() 方法是定義在 jQuery 函數的 prototype 對象上面的,而 $.data()方法是定義 jQuery 函數上面的,調用的時候不從複雜的 jQuery 對象上調用,速度快得多。因此咱們推薦使用第一種寫法。

回到第一種寫法上,就是由於將 this 指向了當前 DOM 元素,咱們才能使用 $(this)將當前 DOM 元素包裝成 jQuery 對象,優雅的使用 hover 方法。

因此最終的 each 源碼爲:

function each(obj, callback) {
    var length, i = 0;

    if (isArrayLike(obj)) {
        length = obj.length;
        for (; i < length; i++) {
            if (callback.call(obj[i], i, obj[i]) === false) {
                break;
            }
        }
    } else {
        for (i in obj) {
            if (callback.call(obj[i], i, obj[i]) === false) {
                break;
            }
        }
    }

    return obj;
}複製代碼

性能比較

咱們在性能上比較下 for 循環和 each 函數:

var arr = Array.from({length: 1000000}, (v, i) => i);

console.time('for')
var i = 0;
for (; i < arr.length; i++) {
    i += arr[i];
}
console.timeEnd('for')


console.time('each')
var j = 0;
$.each(arr, function(index, item){
    j += item;
})
console.timeEnd('each')複製代碼

這裏顯示一次運算的結果:

性能比較
性能比較

從上圖能夠看出,for 循環的性能是明顯好於 each 函數的,each 函數本質上也是用的 for 循環,究竟是慢在了哪裏呢?

咱們再看一個例子:

function each(obj, callback) {
    var i = 0;
    var length = obj.length
    for (; i < length; i++) {
        value = callback(i, obj[i]);
    }
}

function eachWithCall(obj, callback) {
    var i = 0;
    var length = obj.length
    for (; i < length; i++) {
        value = callback.call(obj[i], i, obj[i]);
    }
}

var arr = Array.from({length: 1000000}, (v, i) => i);

console.time('each')
var i = 0;
each(arr, function(index, item){
    i += item;
})
console.timeEnd('each')


console.time('eachWithCall')
var j = 0;
eachWithCall(arr, function(index, item){
    j += item;
})
console.timeEnd('eachWithCall')複製代碼

這裏顯示一次運算的結果:

性能比較
性能比較

each 函數和 eachWithCall 函數惟一的區別就是 eachWithCall 調用了 call,從結果咱們能夠推測出,call 會致使性能損失,但也正是 call 的存在,咱們才能將 this 指向循環中當前的元素。

有舍有得吧。

專題系列

JavaScript專題系列目錄地址:github.com/mqyqingfeng…

JavaScript專題系列預計寫二十篇左右,主要研究平常開發中一些功能點的實現,好比防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特色是研(chao)究(xi) underscore 和 jQuery 的實現方式。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索