1. 程式人生 > 其它 >for 迴圈的 5 種寫法,哪種最快?

for 迴圈的 5 種寫法,哪種最快?

來源:juejin.im/post/5ea63f3ef265da47b177b4b6

幾種遍歷方法中for執行最快,它沒有任何額外的函式呼叫棧和上下文。但在實際開發中我們要結合語義話、可讀性和程式效能,去選擇究竟使用哪種方案。下面來看for , foreach , map , for...in , for...of五種方法現場battle。

for

我是最早出現的一方遍歷語句,在座的各位需稱我一聲爺爺。我能滿足開發人員的絕大多數的需求。

// 遍歷陣列
let arr = [1,2,3];
for(let i = 0;i < arr.length;i++){
    console.log(i) // 索引,陣列下標
    console.log(arr[i]) // 陣列下標所對應的元素
}

// 遍歷物件
let profile = {name:"April",nickname:"二十七刻",country:"China"};
for(let i = 0, keys=Object.keys(profile); i < keys.length;i++){
    console.log(keys[i]) // 物件的鍵值
    console.log(profile[keys[i]]) // 物件的鍵對應的值
}

// 遍歷字串
let str = "abcdef";
for(let i = 0;i < str.length ;i++){
    console.log(i) // 索引 字串的下標
    console.log(str[i]) // 字串下標所對應的元素
}

// 遍歷DOM 節點
let articleParagraphs = document.querySelectorAll('.article > p');
for(let i = 0;i<articleParagraphs.length;i++){
    articleParagraphs[i].classList.add("paragraph");
    // 給class名為“article”節點下的 p 標籤新增一個名為“paragraph” class屬性。
}

forEach

我是ES5版本釋出的。按升序為陣列中含有效值的每一項執行一次 callback 函式,那些已刪除或者未初始化的項將被跳過(例如在稀疏陣列上)。我是 for 迴圈的加強版。

// 遍歷陣列
let arr = [1,2,3];
arr.forEach(i => console.log(i))

// logs 1
// logs 2
// logs 3
// 直接輸出了陣列的元素

//遍歷物件
let profile = {name:"April",nickname:"二十七刻",country:"China"};
let keys = Object.keys(profile);
keys.forEach(i => {
    console.log(i) // 物件的鍵值
    console.log(profile[i]) // 物件的鍵對應的值
})

map

我也是ES5版本釋出的,我可以建立一個新陣列,新陣列的結果是原陣列中的每個元素都呼叫一次提供的函式後的返回值。

let arr = [1,2,3,4,5];
let res = arr.map(i => i * i);

console.log(res) // logs [1, 4, 9, 16, 25]

for...in列舉

我是ES5版本釋出的。以任意順序遍歷一個物件的除Symbol以外的可列舉屬性。

// 遍歷物件
let profile = {name:"April",nickname:"二十七刻",country:"China"};
for(let i in profile){
    let item = profile[i];
    console.log(item) // 物件的鍵值
    console.log(i) // 物件的鍵對應的值

// 遍歷陣列
let arr = ['a','b','c'];
for(let i in arr){
    let item = arr[i];
    console.log(item) // 陣列下標所對應的元素
    console.log(i) // 索引,陣列下標

// 遍歷字串
let str = "abcd"
for(let i in str){
    let item = str[i];
    console.log(item) // 字串下標所對應的元素
    console.log(i) // 索引 字串的下標
}

for...of迭代

我是ES6版本釋出的。在可迭代物件(包括 Array,Map,Set,String,TypedArray,arguments 物件等等)上建立一個迭代迴圈,呼叫自定義迭代鉤子,併為每個不同屬性的值執行語句。

// 迭代陣列陣列
let arr = ['a','b','c'];
for(let item of arr){
    console.log(item)
}
// logs 'a'
// logs 'b'
// logs 'c'

// 迭代字串
let str = "abc";
for (let value of str) {
    console.log(value);
}
// logs 'a'
// logs 'b'
// logs 'c'

// 迭代map
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]
for (let entry of iterable) {
    console.log(entry);
}
// logs ["a", 1]
// logs ["b", 2]
// logs ["c", 3]

// 迭代map獲取鍵值
for (let [key, value] of iterable) {
    console.log(key)
    console.log(value);
}


// 迭代set
let iterable = new Set([1, 1, 2, 2, 3, 3,4]);
for (let value of iterable) {
    console.log(value);
}
// logs 1
// logs 2
// logs 3
// logs 4

// 迭代 DOM 節點
let articleParagraphs = document.querySelectorAll('.article > p');
for (let paragraph of articleParagraphs) {
    paragraph.classList.add("paragraph");
    // 給class名為“article”節點下的 p 標籤新增一個名為“paragraph” class屬性。
}

// 迭代arguments類陣列物件
(function() {
  for (let argument of arguments) {
    console.log(argument);
  }
})(1, 2, 3);
// logs:
// 1
// 2
// 3


// 迭代型別陣列
let typeArr = new Uint8Array([0x00, 0xff]);
for (let value of typeArr) {
  console.log(value);
}
// logs:
// 0
// 255

經過第一輪的自我介紹和技能展示後,我們瞭解到:

  • for語句是最原始的迴圈語句。定義一個變數i(數字型別,表示陣列的下標),按照一定的條件,對i進行迴圈累加。條件通常為迴圈物件的長度,當超過長度就停止迴圈。因為物件無法判斷長度,所以搭配Object.keys()使用。
  • forEach ES5 提出。自稱是for語句的加強版,可以發現它比for語句在寫法上簡單了很多。但是本質上也是陣列的迴圈。forEach每個陣列元素執行一次 callback 函式。也就是呼叫它的陣列,因此,不會改變原陣列。返回值是undefine
  • map ES5 提出。給原陣列中的每個元素都按順序呼叫一次 callback 函式。生成一個新陣列,不修改呼叫它的原陣列本身。返回值是新的陣列。
  • for...in ES5 提出。遍歷物件上的可列舉屬性,包括原型物件上的屬性,且按任意順序進行遍歷,也就是順序不固定。遍歷陣列時把陣列的下標當作鍵值,此時的i是個字串型的。它是為遍歷物件屬性而構建的,不建議與陣列一起使用。
  • for...of ES6 提出。只遍歷可迭代物件的資料。

能力甄別

作為一個程式設計師,僅僅認識他們是遠遠不夠的,在實際開發中鑑別他們各自的優缺點。因地制宜的使用他們,揚長避短。從而提高程式的整體效能才是能力之所在。

關於跳出迴圈體

在迴圈中滿足一定條件就跳出迴圈體,或者跳過不符合條件的資料繼續迴圈其它資料。是經常會遇到的需求。常用的語句是breakcontinue

簡單的說一下二者的區別,就當複習好了。

  • break語句是跳出當前迴圈,並執行當前迴圈之後的語句;
  • continue語句是終止當前迴圈,並繼續執行下一次迴圈;

注意forEachmap 是不支援跳出迴圈體的,其它三種方法均支援。

原理 :檢視forEach實現原理,就會理解這個問題。

Array.prototype.forEach(callbackfn [,thisArg]{
    
}

傳入的function是這裡的回撥函式。在回撥函式裡面使用break肯定是非法的,因為break只能用於跳出迴圈,回撥函式不是迴圈體。

在回撥函式中使用return,只是將結果返回到上級函式,也就是這個for迴圈中,並沒有結束for迴圈,所以return也是無效的。

map() 同理。

map()鏈式呼叫

map() 方法是可以鏈式呼叫的,這意味著它可以方便的結合其它方法一起使用。例如:reduce(), sort(), filter() 等。但是其它方法並不能做到這一點。forEach()的返回值是undefined,所以無法鏈式呼叫。

// 將元素乘以本身,再進行求和。
let arr = [1, 2, 3, 4, 5];
let res1 = arr.map(item => item * item).reduce((total, value) => total + value);

console.log(res1) // logs 55 undefined"

for...in會遍歷出原型物件上的屬性

Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};
var arr = ['a', 'b', 'c'];
arr.foo = 'hello
for (var i in arr) {
    console.log(i);
}
// logs
// 0
// 1
// 2
// foo
// arrCustom
// objCustom

然而在實際的開發中,我們並不需要原型物件上的屬性。這種情況下我們可以使用hasOwnProperty() 方法,它會返回一個布林值,指示物件自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。如下:

Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};
var arr = ['a', 'b', 'c'];
arr.foo = 'hello
for (var i in arr) {
    if (arr.hasOwnProperty(i)) {
        console.log(i);
    }
}
// logs
// 0
// 1
// 2
// foo

// 可見陣列本身的屬性還是無法擺脫。此時建議使用 forEach

對於純物件的遍歷,選擇for..in列舉更方便;對於陣列遍歷,如果不需要知道索引for..of迭代更合適,因為還可以中斷;如果需要知道索引,則forEach()更合適;對於其他字串,類陣列,型別陣列的迭代,for..of更佔上風更勝一籌。但是注意低版本瀏覽器的是配性。

效能

有興趣的讀者可以找一組資料自行測試,文章就直接給出結果了,並做相應的解釋。

for > for-of > forEach > map > for-in
  • for 迴圈當然是最簡單的,因為它沒有任何額外的函式呼叫棧和上下文;
  • for...of只要具有Iterator介面的資料結構,都可以使用它迭代成員。它直接讀取的是鍵值。
  • forEach,因為它其實比我們想象得要複雜一些,它實際上是array.forEach(function(currentValue, index, arr), thisValue)它不是普通的 for 迴圈的語法糖,還有諸多引數和上下文需要在執行的時候考慮進來,這裡可能拖慢效能;
  • map() 最慢,因為它的返回值是一個等長的全新的陣列,陣列建立和賦值產生的效能開銷很大。
  • for...in需要窮舉物件的所有屬性,包括自定義的新增的屬性也能遍歷到。且for...in的key是String型別,有轉換過程,開銷比較大。

總結

在實際開發中我們要結合語義話、可讀性和程式效能,去選擇究竟使用哪種方案。

如果你需要將陣列按照某種規則對映為另一個數組,就應該用 map。

如果你需要進行簡單的遍歷,用 forEach 或者 for of。

如果你需要對迭代器進行遍歷,用 for of。

如果你需要過濾出符合條件的項,用 filterr。

如果你需要先按照規則對映為新陣列,再根據條件過濾,那就用一個 map 加一個 filter。

總之,因地制宜,因時而變。千萬不要因為過分追求效能,而忽略了語義和可讀性。在您的統治之下,他們5個只能是各自發揮長處,誰都別想稱霸。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2021最新版)

2.別在再滿屏的 if/ else 了,試試策略模式,真香!!

3.臥槽!Java 中的 xx ≠ null 是什麼新語法?

4.Spring Boot 2.5 重磅釋出,黑暗模式太炸了!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!