JavaScript中forEach的錯誤用法匯總
前言
使用過forEach的人大致有兩種:普通使用,簡簡單單;復(fù)雜使用,總想搞出點花樣來,結(jié)果一些莫名其妙的bug就出現(xiàn)了,解決這些bug所花費的時間都可以換一種思路實現(xiàn)了,能用作for循環(huán)的,又不只是forEach。沒錯,筆者就是后者,終究是自己“學(xué)藝不精”。于是乎,花點時間,結(jié)合自己的實際開發(fā)經(jīng)驗,再來好好理理forEach。
語法
forEach()是數(shù)組對象的一個原型方法,該方法會對數(shù)組中的每一個元素執(zhí)行一次給定的回調(diào)函數(shù),并且始終返回undefined。類數(shù)組對象是沒有forEach方法的,例如arguments。forEach的用法比較簡單:
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
實際例子:
const arr = [1,2,3]; arr.forEach(item => console.log(item)); // 1,2,3
參數(shù)說明:
callback:數(shù)組中每一個元素將要執(zhí)行的回調(diào)函數(shù),可以有1-3個參數(shù)
- currentValue:當(dāng)前正在處理的元素,必傳
- index:當(dāng)前正在處理的元素的索引,可選參數(shù)
- array:forEach方法操作的原數(shù)組對象,可選參數(shù)
thisArg:當(dāng)前執(zhí)行callback回調(diào)函數(shù)時,回調(diào)函數(shù)的this指向,默認(rèn)指向的全局對象,可選參數(shù)
語法看起來并不復(fù)雜,那么看看是否會犯下面的錯誤用法:
錯誤用法
錯誤用法指的是標(biāo)題所描述的操作,不是正文內(nèi)容,切記,切記。
添加或刪除原數(shù)組中的數(shù)據(jù)
forEach()在第一次調(diào)用callback時就會確定遍歷的范圍,一般來說會按照索引升序為數(shù)組中的有效元素執(zhí)行一次callback函數(shù)。如果是未初始化的數(shù)組項或者在調(diào)用forEach()后添加到數(shù)組中的項都不會被訪問到,如果在遍歷時刪除數(shù)組中的元素,則可能會出現(xiàn)意外的情況。
無效的值會直接跳過
調(diào)用后添加元素,將不會被訪問到
const arr = [1,,,2,4];
console.log(arr.length); // 5
let callbackCounts = 0;
arr.forEach(item => {
console.log(item); // 1 2 4
callbackCounts++;
arr.push(6); // 調(diào)用后添加元素,將不會被訪問到
})
console.log(callbackCounts); // 3
console.log(arr); // [1,,,2,4,6,6,6]刪除數(shù)組中的元素
const arr = ['a', 'b', 'c', 'd'];
console.log(arr.length); // 4
let callbackCounts = 0;
arr.forEach((item, index) => {
// arr.shift();
console.log(item); // 'a','b','d',其中'c'會被跳過
if (item === 'b') {
// arr.shift(); // 刪除頭部,arr的結(jié)果:['b', 'c', 'd']
arr.splice(index, 1); // 刪除當(dāng)前元素,arr的結(jié)果:['a', 'c', 'd']
// arr.splice(-1); // 刪除最后一個元素,arr的結(jié)果:['a', 'b', 'c']
}
callbackCounts++;
})
console.log(callbackCounts); // 3
console.log(arr); // ['b', 'c', 'd']刪除元素時情況可能會比較復(fù)雜一點,感興趣的朋友可以自己測試,我們可以這么理解:
- forEach在調(diào)用時就確定了遍歷范圍,傳遞給callback的值是forEach在遍歷到該元素時的值,即使在callback中刪除了數(shù)組中該元素,但是值已經(jīng)傳遞進(jìn)來
- 如果是有效的值,callbackCounts會遞增。在下一輪循環(huán)時,forEach會根據(jù)上一輪循環(huán)時的索引得到當(dāng)前循環(huán)對應(yīng)的值,注意此時數(shù)組長度已經(jīng)改變,就會出現(xiàn)“跳過”的現(xiàn)象
- 然后重復(fù)操作,知道數(shù)組遍歷完畢
搞懂了”跳過“,你還敢想當(dāng)然的刪除數(shù)組中的數(shù)據(jù)嗎?
修改原數(shù)組中的數(shù)據(jù)
既然不能添加和刪除,那我要是修改呢?其實修改數(shù)組中的元素,不是不可以,只是要注意使用方法。
如果數(shù)組中是基本數(shù)據(jù)類型:string、number、boolean等,只使用回調(diào)函數(shù)的第一個參數(shù)修改數(shù)組中的值是不會影響原數(shù)組的
const arr = [1,2,3,4,5] arr.forEach((item, index, array) => { item+=1; // [1,2,3,4,5] // arr[index]+=1; // [2,3,4,5,6] // array[index]+=1; // [2,3,4,5,6] }) console.log(arr);如果數(shù)組中的是引用數(shù)據(jù)類型:object等,直接替換數(shù)組項是不會影響原數(shù)組的
const arr = [ {name: '張三', id: 1}, {name: '李四', id: 2} ] arr.forEach((item, index, array) => { if (item.id === 2) { item = {name: '王五', id: item.id}; // 張三、李四 // Object.assign(item, {name: '王五', id: item.id}); // 張三、王五 // arr[index] = {name: '王五', id: item.id}; // 張三、王五 // array[index] = {name: '王五', id: item.id}; // 張三、王五 } }) console.log(arr);數(shù)組對象在遍歷時,實際上是將數(shù)組項的引用地址賦值給
item,如果將另一個對象的引用地址重新賦值給item,并不會改變原引用地址的數(shù)據(jù),也就不會影響原數(shù)組。如果數(shù)組中的是引用數(shù)據(jù)類型:object等,此時我們只修改數(shù)組項的某一個屬性,這個時候是會影響原數(shù)組的
const arr = [ {name: '張三', id: 1}, {name: '李四', id: 2} ] arr.forEach((item, index, array) => { if (item.id === 2) { item.name = '王五'; // arr[index].name = '王五'; // 張三、王五 // array[index].name = '王五'; // 張三、王五 } }) console.log(arr); // 張三、王五道理呢也和2類似,
item指向的是引用地址,修改屬性相當(dāng)于是修改了引用地址中對象的屬性,也就會修改原數(shù)組
綜上我們可以發(fā)現(xiàn),如果要在forEach中修改原數(shù)組,那么需要在其回調(diào)函數(shù)中,通過索引index或者借助Object.assgin()才可以實現(xiàn),最終原理都是修改引用地址中的數(shù)據(jù),而不是直接修改。
回調(diào)函數(shù)中使用異步函數(shù)
異步函數(shù)和同步函數(shù)的執(zhí)行順序此處就不細(xì)說,詳情可以移步:搞不清楚事件循環(huán),那就看看這篇文章,簡單來說就是同步代碼先于異步代碼執(zhí)行。
看一個例子:
const arr = [1,2,3,4,5]
let sum = 0;
let callbackCounts = 0;
function Sum(a, b) {
return new Promise((resovle) => {
resovle(a + b)
})
}
arr.forEach(async (item, index, array) => {
sum = await Sum(sum, item)
})
console.log(sum); // 0實際得到的求和的值并不是我們期待的15,而是0。
如果我們需要實現(xiàn)異步求和,可以使用for循環(huán)實現(xiàn):
const arr = [1,2,3,4,5]
let sum = 0;
let callbackCounts = 0;
function Sum(a, b) {
return new Promise((resovle) => {
resovle(a + b)
})
}
(async function() {
for (let item of arr) {
sum = await Sum(sum, item)
}
console.log(sum); // 15
})();使用return結(jié)束循環(huán)
在使用for循環(huán)時,我們一般可使用break、return來跳出循環(huán),拋出異常也可以,但是這不是正常的開發(fā)流程。我們來試一下在forEach中使用break、return有沒有作用:
forEach結(jié)束循環(huán)
const arr = [1,2,3,4,5]
let callbackCounts = 0;
arr.forEach((item, index, array) => {
callbackCounts++;
if (item === 2) {
return; // forEach中不能使用break,即使使用return,也無法中止循環(huán)
}
})
console.log(arr.length, callbackCounts); // 5 5如果非得要跳出forEach循環(huán),首先建議的是使用其他循環(huán)方法,例如:for、for of、for in、map等,其次我們可以考慮拋出一個異常來跳出forEach循環(huán):
const arr = [1,2,3,4,5]
let callbackCounts = 0;
try {
arr.forEach((item, index, array) => {
callbackCounts++;
if (item === 2) {
throw 'throw forEach';
}
})
} catch (e) {
console.log(arr.length, callbackCounts); // 5 2
}如果真要使用throw來拋出異常,那么使用其他循環(huán)方法不香嗎
未傳入this
forEach()也可能存在this指向問題,例如:
function Counter() {
this.sum = 0;
}
Counter.prototype.add = function (array) {
array.forEach(function(element) {
this.sum += element;
});
}
const obj = new Counter();
obj.add([1,2,3,4,5])
console.log(obj.sum); // 0未指定this,則默認(rèn)未window對象,此時的this.sum為undefined,而我們想的是this指向傳入的數(shù)組。那么需要傳入this或者使用箭頭函數(shù)。
array.forEach((element) => {
this.sum += element;
});
array.forEach(function(element) {
this.sum += element;
}, this);正確用法
避免錯誤用法,當(dāng)然就是正確用法咯。
其實forEach在設(shè)計出來只是為了簡化for循環(huán)的遍歷,如果要過多的進(jìn)行其他操作,就違背了設(shè)計初衷了。每一個API都有自己的適用范圍,如果堅持要一把梭,可能就會踩很多坑。
簡單總結(jié)一下正確的使用方法:
- 最好只限于遍歷原數(shù)組,不涉及修改原數(shù)組中的數(shù)據(jù)
- 避免在回調(diào)函數(shù)中存在異步操作
- 不能使用return
forEach()的回調(diào)函數(shù)使用箭頭函數(shù),可避免this指向問題
總結(jié)
forEach本身并不會改變原數(shù)組,但是其回調(diào)函數(shù)可能會修改。如果真要修改原數(shù)組,建議使用map、filter等方法forEach方法始終返回undefined,這使得forEach無法像map、filter一樣可以鏈?zhǔn)秸{(diào)用- 除了拋出異常外,
forEach無法被中止或者跳出循環(huán),如果要跳出循環(huán),建議使用其他for循環(huán)方法 - 如果是涉及異步函數(shù),可以考慮使用
for await of代替forEach
到此這篇關(guān)于JavaScript中forEach錯誤用法的文章就介紹到這了,更多相關(guān)js forEach錯誤用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript-表格排序(降序/反序)實現(xiàn)介紹(附圖)
使用了Array方法、sort:降序、reverse:反序完成了基本功能,對于聯(lián)合排序沒有實現(xiàn),感興趣的朋友可以參考下哈2013-05-05
編寫跨瀏覽器的javascript代碼必備[js多瀏覽器兼容寫法]
下面比較了幾種瀏覽器之間的差異,在寫javascript代碼時 要時刻注意這些差異2008-10-10
js中判斷一個數(shù)是不是素數(shù)的三種方法例子
這篇文章主要給大家介紹了關(guān)于js中如何判斷一個數(shù)是不是素數(shù)的三種方法,素數(shù)(只能被1和本身整除的數(shù))規(guī)律:把這個數(shù)除以它之前的每一個數(shù)(從2開始)只要找到一個整除(余數(shù)為0)就是非素數(shù),需要的朋友可以參考下2023-10-10
JavaScript實現(xiàn)網(wǎng)頁留言板功能
這篇文章主要為大家詳細(xì)介紹了JavaScript實現(xiàn)網(wǎng)頁留言板功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-11-11
TypeScript里string和String的區(qū)別
這篇文章主要介紹了TypeScript里string和String的區(qū)別,真的不止是大小寫的區(qū)別,string表示原生類型,而String表示對象,下文更多詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-03-03
textarea不能通過maxlength屬性來限制字?jǐn)?shù)的解決方法
textarea稱文本域,又稱文本區(qū),其不能通過maxlength屬性來限制字?jǐn)?shù),為此必須尋求其他方法來加以限制以達(dá)到預(yù)設(shè)的需求2014-09-09
JavaScript 實現(xiàn)打印,打印預(yù)覽,打印設(shè)置
這篇文章主要介紹了JavaScript 實現(xiàn)打印,打印預(yù)覽,打印設(shè)置的方法及示例分享,需要的朋友可以參考下2014-12-12
Laydate時間組件在火狐瀏覽器下有多時間輸入框時只能給第一個輸入框賦值的解決方法
這篇文章主要介紹了Laydate時間組件在火狐瀏覽器下有多時間輸入框時只能給第一個輸入框賦值的解決方法,需要的朋友可以參考下2016-08-08

