當年懵懂無知的我被問到這個問題時,腦袋一片空白,因為我一度認為forEach隻是為了友善書寫所創造出來的文法糖,在業務代碼中也經常使用,但沒有思考過它存在的問題,本文旨在記錄自己的心路曆程,抛磚引玉,如果對你有所幫助那就更好啦。
那麼回到标題,首先forEach是不能使用任何手段跳出循環的,想知道問題答案的看官此時可以不用繼續看了。
為什麼呢?我們知道forEach接收一個函數,它一般有兩個參數,第一個是循環的目前元素,第二個是該元素對應的下标,手動實作一下僞代碼:
Array.prototype.myForEach = function (fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i], i, this);
}
}
forEach是不是真的這麼實作我無從考究,但是以上這個簡單的僞代碼确實滿足forEach的特性,而且也很明顯就是不能跳出循環,因為根本沒有辦法操作到真正的for循環體。
後來經過查閱文檔,發現官方對forEach的定義根本不是我認為的文法糖,它的标準說法是forEach為每個數組元素執行一次你所提供的函數。官方文檔也有這麼一段話:
除抛出異常之外,沒有其他方法可以停止或中斷循環。如果您需要這種行為,則該forEach()方法是錯誤的工具。
使用抛出異常來跳出foreach循環
let arr = [0, 1, "stop", 3, 4];
try {
arr.forEach(element {
if (element === "stop") {
throw new Error("forEachBreak");
}
console.log(element); // 輸出 0 1 後面不輸出
});
} catch (e) {
console.log(e.message); // forEachBreak
那麼可不可以認為,forEach可以跳出循環,使用抛出異常就可以了?這點我認為仁者見仁智者見智吧,在forEach的設計中并沒有中斷循環的設計,而使用try-catch包裹時,當循環體過大性能會随之下降,這是無法避免的,是以抛出異常可以作為一種中斷forEach的手段,但并不是為解決forEach問題而存在的銀彈。
再次回歸到開頭寫的那段僞代碼,對它進行一些優化,在真正的for循環中加入對傳入函數的判斷:
// 為避免争議此處不再覆寫原有forEach函數
Array.prototype.myForEach = function (fn) {
for (let i = 0; i < this.length; i++) {
let ret = fn(this[i], i, this);
if (typeof ret !== "undefined" && (ret == null || ret == false)) break;
}
}
這樣的話就能根據return值來進行循環跳出啦:
let arr = [0, 1, "stop", 3, 4];
arr.myForEach(x {
if (x === 'stop') return false
console.log(x); // 輸出 0 1 後面不輸出
});
// return即為continue:
arr.myForEach(x {
if (x === 'stop') return
console.log(x); // 0 1 3 4
文檔中還提到forEach需要一個同步函數,也就是說在使用異步函數或Promise作為回調時會發生預期以外的結果,是以forEach還是需要慎用。
Array.prototype.find()
Array.prototype.findIndex()
Array.prototype.map()
Array.prototype.filter()
Array.prototype.every()
Array.prototype.some()