JavaScript 實現循環遍歷的方法總結 以及 經常使用方法性能比較( Duff‘s Device)

1、實現循環遍歷的方法

原生JS實現遍歷:

1. While

While 語句包括一個循環條件和一段代碼塊,只要條件爲真,就不斷循環執行代碼塊。javascript

while (條件) { 執行代碼; }; 
複製代碼

2. do...While

do...while 循環與 while 循環相似,惟一的區別就是先運行一次循環體,而後判斷循環條件。html

do { 
    執行代碼; 
} while (條件);
複製代碼

3. for

普通 for 循環,常常用的數組遍歷。使用臨時變量,將長度緩存起來,避免重複獲取數組長度,當數組較大時優化效果纔會比較明顯。java

for(j = 0; j < arr.length; j++) {
    // 代碼執行
}
複製代碼

4. for ... in

for…in 循環通常用於對象的遍歷,不適用於遍歷數組。算法

可是這裏有一個坑須要注意: 任何對象都繼承了Object對象,或者其它對象,繼承的類的屬性是默認不可遍歷的,for... in 循環遍歷的時候會跳過,可是這個屬性是能夠更改成能夠遍歷的,那麼就會形成遍歷到不屬於自身的屬性。數組

var obj = {a: 1, b: 2, c: 3}; 

for (var i in obj) { 
    console.log('鍵名:', i); 
    console.log('鍵值:', obj[i]); 
} 
// 鍵名:a 鍵值:1 鍵名:b 鍵值:2
// 其中 obj爲循環的對象, i 爲對象中的「鍵名」。若是對象是數組,那麼i就是索引。
複製代碼

若是繼承的屬性是可遍歷的,那麼就會被for...in循環遍歷到。但若是隻想遍歷自身的屬性,使用for...in的時候,應該結合使用hasOwnProperty() 方法,在循環內部判斷一下,某個屬性是否爲對象自身的屬性。不然就能夠產生遍歷失真的狀況。緩存

遍歷數組的缺點:數組的下標 index 值是數字,for-in遍歷的 index值"0","1","2"等是字符串。markdown

5. for ... of

for...of 遍歷 是ES6新增功能函數

for( let i of arr){
    console.log(i);
}
複製代碼

for...of 循環不只支持數組,還支持大多數類數組對象,也支持字符串遍歷。 for...of 它能夠正確響應 breakcontinuereturn 語句。oop

6. forEach

ES5推出的,數組自帶的循環,主要功能是遍歷數組,實際性能比for還弱。性能

arr.forEach(function(item, index){
  console.log('forEach遍歷:' + index + '--' + item);
})
複製代碼

forEach 不能使用 break 語句中斷循環,也不能使用 return 語句返回到外層函數。

7. map()

map() 方法將數組的全部成員依次傳入參數函數,而後把每一次的執行結果組成一個新數組返回。

arr.map(function(value, index){
    console.log('map遍歷:' + index + '--' + value);
});	 
// map遍歷支持使用return語句,支持return返回值
var temp = arr.map(function(val, index){
  console.log(val); 
  return val * val;          
})
console.log(temp);
複製代碼

注意:map() 是返回一個新數組,而不會改變原數組。

8. filter()

filter() 方法用於過濾數組成員,知足條件的成員組成一個新數組返回。它的參數是一個函數,全部數組成員依次執行該函數,返回結果爲true的成員組成一個新數組返回。該方法不會改變原數組。

var arr = [73, 84, 56, 22, 100];
var newArr = arr.filter(item => item>80)
// 獲得新數組 newArr = [84, 100],原數組arr沒有改變。
複製代碼

9. every()

every() 是對數組中的每一項運行指定函數來判斷數組成員是否符合某種條件,若是該函數對每一項返回true,則返回true。

var arr = [ 1, 2, 3, 4, 5, 6 ]; 
arr.every( function( item, index, array ){ 
    return item > 3; 
}); 
// false
複製代碼

10. some()

some() 與上面得 every() 相反,若是該函數對任一項返回 true,則整個 some() 方法的返回值就是 true,不然返回 false。

var arr = [ 1, 2, 3, 4, 5, 6 ]; 
arr.every( function( item, index, array ){ 
    return item > 3; 
}); 
// true
複製代碼

二者比較,some() 只要有一個是 true,便返回 true;而 every() 只要有一個是 false,便返回 false。

11. reduce() 和 reduceRight()

reduce() 方法和 reduceRight() 接收一個函數做爲累加器,函數有四個參數,分別是:上一次的值,當前值,當前值的索引,數組。 它們的差異是,reduce() 是從左到右處理(從第一個成員到最後一個成員),reduceRight() 則是從右到左(從最後一個成員到第一個成員),其餘徹底同樣。

var total = [0, 1, 2, 3, 4];
// 這四個參數之中,只有前兩個是必須的,後兩個則是可選的。
total.reduce(function( previousValue, currentValue){
    return previousValue + currentValue;
});
// 若是要對累積變量指定初值,能夠把它放在reduce方法和reduceRight方法的第二個參數。
total.reduce(function( a, b){
    return a + b;
}, 10);
// 上面的第二個參數至關於設定了默認值,處理空數組時尤爲有用,可避免一些空指針異常。
複製代碼

12. find()

find() 方法返回數組中符合函數條件的第一個元素。不然返回 undefined

var stu = [{ name: '張三', }, { name: '王小毛', }, { name: '李四', age: 18}]
stu.find((item) => (item.name === '李四')) 
// {name: "李四", age: 18}
複製代碼

13. findIndex()

對於數組中的每一個元素,findIndex() 方法都會調用一次回調函數(採用升序索引順序),直到有元素返回 true。只要有一個元素返回 true,findIndex() 當即返回該返回 true 的元素的索引值。若是數組中沒有任何元素返回 true,則 findIndex() 返回 -1。

findIndex() 不會改變數組對象。

[1,2,3].findIndex(x => x === 2 );  // 1
[1,2,3].findIndex(x => x === 4 );  // -1
複製代碼

14. ES6 新增:entries(),keys() 和 values()

entries()keys()values() —— 用於遍歷數組。它們都返回一個遍歷器對象,能夠用 for...of 循環進行遍歷。

惟一的區別是 keys() 是對 鍵名 的遍歷、values()是對 鍵值 的遍歷,entries() 是對 鍵值對 的遍歷。

// keys() 鍵名 的遍歷
for (let i of ['a', 'b'].keys()) {
    console.log(i);  // 0 1
}
// values() 鍵值 的遍歷
for (let item of ['a', 'b'].values()) {
    console.log(item);  // a b
}
// entries() 鍵值對 的遍歷
for (let [index, item] of ['a', 'b'].entries()) {
    console.log(index, item);
    // 0 "a" 1 "b"
}
複製代碼

jQuery實現遍歷:

1. jQuery.grep()

$.grep() 函數使用指定的函數過濾數組中的元素,並篩選符合條件的元素,組成新的數組,並返回。

var arr = [ 1, 9, 3, 8, 6, 1, 5, 9, 4, 7, 3, 8, 6, 9, 1 ];
arr = jQuery.grep(arr, function( item, index ) {
// function 中兩個參數,一是當前迭代的數組元素,二是當前迭代元素在數組中的索引。
     if( item !== 5 && index > 4){
       // 返回元素不爲5,且索引大於4的數組
       return true;
      }
});
console.log(arr) // (9) [1, 9, 4, 7, 3, 8, 6, 9, 1]
複製代碼

2. jQuery.each()

$.each() 函數用於遍歷指定的對象和數組。

jQuery.each([52, 97], function(index, value) {
    console.log(index + ': ' + value);
    // 0: 52 // 1: 97
});
複製代碼

3. jQuery.inArray()

$.inArray() 函數在數組中查找指定值,並返回它的索引值(若是沒有找到,則返回-1)。

var anArray = ['one', 'two', 'three'];
var index = $.inArray('two', anArray);
console.log(index); //返回該值在數組中的鍵值,返回1
console.log(anArray[index+1]); // three
複製代碼

4. jQuery.map()

$.map() 函數用於使用指定函數處理數組中的每一個元素(或對象的每一個屬性),並將處理結果封裝爲新的數組返回。

$(function () { 
    var arr = ['0','1','2','3','4','S','6'];
    var values = $.map(arr, function(value){
    var result = new Number(value);
        return isNaN(result) ? null : result; // 若是不是NaN就返回result值
    });
    // 遍歷打印新返回的values
    for (key in values) {
        console.log(values[key]) // 0, 1, 2, 3, 4, 6
    }
})
複製代碼

2、經常使用循環遍歷方法性能比較

在這裏主要對如下幾個經常使用方法進行性能方面的比較:正常的 for 循環、倒序 for 循環、while 循環、for-in 循環、for each 循環 和 Duff's Device 循環。

即便是循環中最快的代碼,累計迭代上千次也會慢下來。此外,循環體運行時也會帶來小性能開銷,不只僅是增長了整體運行時間。減小迭代次數能得到更加顯著的性能提高,最廣爲人知的一種限制循環迭代次數的模式被稱爲「達夫設備(Duff's Device)」。

var num = 10000;  // 數組大小
var itemNum = 1000;  // 迭代次數 100/1000/10000/100000
var array = [];  // 初始化數組
for (var i = 0 ; i < num ; i++ ){
    array[i] = i + 1;
}
var len = array.length;

console.log("迭代次數: " + itemNum);

// 正常for循環
console.time('正常for循環');
for(var j = 0 ; j < itemNum ; j ++) {
    for(var k = 0 ; k < len ; k ++) {
        array[k] + 1;
    }
}
console.timeEnd('正常for循環');


// 倒序for循環
console.time('倒序for循環');
for(let j = 0; j < itemNum; j ++){
    for(let k = len - 1 ; k --;)
    {
        array[k] + 1;
    }
}
console.timeEnd('倒序for循環');


// while循環
console.time('while循環');
for(let j = 0; j < itemNum; j ++){   
    let k = 0;
    while(k < len){
        array[k] + 1;
        k ++;
    }
}
console.timeEnd('while循環');


// for-in循環
console.time('for-in循環');
for(let j = 0; j < itemNum; j ++){
    for(let k in array){
        array[k] + 1;
    }
}
console.timeEnd('for-in循環');


// for each 循環
console.time("for-each循環");
for(let j =0; j <itemNum; j ++){
    array.forEach((item) => {
        item + 1;
    });
}
console.timeEnd("for-each循環");


// Duff's Device 算法是一種循環體展開技術,它使得一次迭代中實際執行了屢次迭代的操做。
console.time("Duff's Device");
for(let k = 0; k < itemNum; k ++){
    let j = len % 8;
    let tempLen = len-1;
    while(j){
        j--;
        array[tempLen--] + 1;
    }
    j = Math.floor(len / 8);
    while(j){
        j--;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
    }
}
console.timeEnd("Duff's Device");
複製代碼

經過每次循環遍歷,打印每種方式的運行時間,能夠發現:

  • 整體來講 for 循環和 for-each 性能至關,在小數據量時也與 while 性能至關。for-in 性能最差,duff's device 性能最好。大數據量時,while 性能優於 for 循環和 for-each 循環。
  • 對於數據量不大的循環(<1000),不用考慮使用哪種性能更好,可讀性是第一位。
  • 對於有較大數據量的循環和遍歷,若是性能不是瓶頸,那麼普通的 for 循環(或者 while,do-while,for-each)就能夠了,畢竟可讀性強。
  • 對於較大的數據量,若是 Array 的循環操做已經成爲瓶頸,或者性能很是重要,那麼能夠採用 duff's device 方案。
    • Duff's device 背後的基本理念是:每次循環中最多可8次調用執行函數。循環迭代次數爲元素總數除以8,由於總數不必定是8的整數倍,因此聲明變量 j 存放餘數,指出第一次循環中應當執行多少次。比方說如今有12個元素,那麼第一次循環將調用執行函數4次,第二次循環調用執行函數8次,用2次循環代替了12次循環。
    • 如何使用該方案,很大程度上依賴於迭代的次數。若是循環迭代次數少於1000次,你可能只看到它與普通循環相比只有微不足道的性能提高。若是迭代次數超過1000次,Duff's device 的效率明顯提高。例如500000次迭代中,運行時間比普通循環減小到70%。

參考文章: www.cnblogs.com/libin-1/p/6… blog.csdn.net/cengjingcan…

相關文章
相關標籤/搜索