一文完全弄懂 for forEach for-in for-of 的區別

基本語法

下面列出了這幾個遍歷語法規則:javascript

for (let index = 0; index < array.length; index++) {
    const element = array[index]
    // ...
}

array.forEach(element => {
    // ...
})

for (const key in array) {
    // ...
}

for (const iterator of array) {
    // ...
}
複製代碼

分狀況討論這幾種寫法的不一樣

非數字的屬性

在 JavaScript 中全部的數組都是對象,這意味着你能夠給數組添加字符串屬性:html

array = ['a', 'b', 'c']

array.test = 'testing'
console.log(array) // [ 'a', 'b', 'c', test: 'testing' ]
複製代碼

若是打印,那麼這個 test 也會被打印出來java

在瀏覽器中,使用 console.table(array) 打印這個數組能夠看到,這個對象中 test 爲 index,testing 爲 value;其餘數組項的 index 值均爲數字編程

20190228105221.png

上述提到的幾個遍歷方法中只有 for-in 循環纔可以打印出這個鍵值對:數組

for (const key in array) {
    console.log(array[key])
}
複製代碼

實際應用的問題

一般狀況下,不建議使用 for-in 來遍歷數組,除非你知道這個數組對象中沒有這樣的屬性瀏覽器

數組空項

假設要遍歷的數組張這樣:array = ['a', , 'c']異步

// a undefined c
for (let index = 0; index < array.length; index++) {
    const element = array[index]
    console.log(element) // 沒有跳過空值
}

// a c
array.forEach(element => {
    console.log(element) // 跳過空值
})

// a c
for (const key in array) {
    console.log(array[key]) // 跳過空值
}

// a undefined c
for (const iterator of array) {
    console.log(iterator) // 沒有跳過空值
}
複製代碼

上面幾個遍歷方法,只有 forEach 和 for-in 遍歷會跳過空值,值得注意的是,若是空值明確設置爲 undefined 如 ['a', undefined, 'c'] 那麼全部遍歷方法都可以將 undefined 遍歷出來async

實際應用的問題

在 JSON 中是不支持這樣的空值的,若是在 parse 方法調用時傳入的 JSON 字符串數據含有空值,會報錯:異步編程

JSON.parse('["a", , "c"]')
// 因此建議使用 for-of 或 for 循環進行遍歷,由於若是
複製代碼
  • stringify 方法調用時,空值會被轉爲 null 非空值或 undefined
  • 正確的作法應該是保持 undefined,遍歷使用 for-of 或 for 循環

建議使用 for-of函數

方法 this 指向的上下文

在 forEach 中須要傳入一個函數,這個函數的 this 指向因語法形式而變化:

for (let index = 0; index < array.length; index++) {
    const element = array[index]
    console.log(this) // {}
}

array.forEach(function (element) {
    console.log(this) // undefined
})

array.forEach(element => {
    console.log(this) // {}
})

for (const key in array) {
    console.log(this) // {}
}

for (const iterator of array) {
    console.log(this) // {}
}
複製代碼

上述遍歷寫法,只有 forEach 在傳入非箭頭函數的時候會出現不一致的狀況

建議使用箭頭函數

Async/Await

async 異步編程中 forEach 則不會按照預期執行,以下:

// a undefined c
{(async () => {
    for (const iterator of array) {
        const result = await new Promise(res => setTimeout(() => { res(iterator) }, 1000))
        console.log(result)
    }
})()}

// a c
{(async () => {
    for (const key in array) {
        const result = await new Promise(res => setTimeout(() => { res(array[key]) }, 1000))
        console.log(result)
    }
})()}

// a undefined c
{(async () => {
    for (let index = 0; index < array.length; index++) {
        const result = await new Promise(res => setTimeout(() => { res(array[index]) }, 1000))
        console.log(result)
    }
})()}

// 語法錯誤
{(async () => {
    array.forEach(element => {
        const result = await new Promise(res => setTimeout(() => { res(element) }, 1000))
        console.log(result)
    })
})()}
複製代碼

按照上述寫法 forEach 會報錯,首先看一下 forEach 的原理:

本質上 forEach 就像一個 for 循環的包裝:

Array.prototype.forEach = function (callback) {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this)
  }
}
複製代碼

若是按照上述寫法,那麼在回調函數內部調用 await 須要這個回調函數自己也是 async 函數,所以改成以下寫法:

// 語法錯誤
{(async () => {
    array.forEach(async element => {
        const result = await new Promise(res => setTimeout(() => { res(element) }, 1000))
        console.log(result)
    })
})()}
複製代碼

按照這樣寫法,forEach 最後會變成並行執行,而非串行。

所以建議使用 for-of 循環

或者建立一個 forEachAwait 方法:

async function forEachAwait(arr, cb) {
    for (let index = 0; index < array.length; index++) {
        await cb(arr[index], index, arr)
    }
}

// a undefined c
{(async () => {
    forEachAwait(array, async (elem) => {
        const result = await new Promise(res => setTimeout(() => { res(elem) }, 1000))
        console.log(result)
    })
})()}
複製代碼

參考:

歡迎訂閱個人公衆號:

相關文章
相關標籤/搜索