<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用過forEach的人大致有兩種:普通使用,簡簡單單;複雜使用,總想搞出點花樣來,結果一些莫名其妙的bug就出現了,解決這些bug所花費的時間都可以換一種思路實現了,能用作for迴圈的,又不只是forEach。沒錯,筆者就是後者,終究是自己“學藝不精”。於是乎,花點時間,結合自己的實際開發經驗,再來好好理理forEach。
forEach()是陣列物件的一個原型方法,該方法會對陣列中的每一個元素執行一次給定的回撥函數,並且始終返回undefined。類陣列物件是沒有forEach方法的,例如arguments。forEach的用法比較簡單:
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
實際例子:
const arr = [1,2,3]; arr.forEach(item => console.log(item)); // 1,2,3
引數說明:
callback:陣列中每一個元素將要執行的回撥函數,可以有1-3個引數
thisArg:當前執行callback回撥函數時,回撥函數的this指向,預設指向的全域性物件,可選引數
語法看起來並不複雜,那麼看看是否會犯下面的錯誤用法:
錯誤用法指的是標題所描述的操作,不是正文內容,切記,切記。
forEach()在第一次呼叫callback時就會確定遍歷的範圍,一般來說會按照索引升序為陣列中的有效元素執行一次callback函數。如果是未初始化的陣列項或者在呼叫forEach()後新增到陣列中的項都不會被存取到,如果在遍歷時刪除陣列中的元素,則可能會出現意外的情況。
無效的值會直接跳過
呼叫後新增元素,將不會被存取到
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); // 呼叫後新增元素,將不會被存取到 }) console.log(callbackCounts); // 3 console.log(arr); // [1,,,2,4,6,6,6]
刪除陣列中的元素
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的結果:['b', 'c', 'd'] arr.splice(index, 1); // 刪除當前元素,arr的結果:['a', 'c', 'd'] // arr.splice(-1); // 刪除最後一個元素,arr的結果:['a', 'b', 'c'] } callbackCounts++; }) console.log(callbackCounts); // 3 console.log(arr); // ['b', 'c', 'd']
刪除元素時情況可能會比較複雜一點,感興趣的朋友可以自己測試,我們可以這麼理解:
搞懂了”跳過“,你還敢想當然的刪除陣列中的資料嗎?
既然不能新增和刪除,那我要是修改呢?其實修改陣列中的元素,不是不可以,只是要注意使用方法。
如果陣列中是基本資料型別:string、number、boolean等,只使用回撥函數的第一個引數修改陣列中的值是不會影響原陣列的
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);
如果陣列中的是參照資料型別:object等,直接替換陣列項是不會影響原陣列的
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);
陣列物件在遍歷時,實際上是將陣列項的參照地址賦值給item
,如果將另一個物件的參照地址重新賦值給item
,並不會改變原參照地址的資料,也就不會影響原陣列。
如果陣列中的是參照資料型別:object等,此時我們只修改陣列項的某一個屬性,這個時候是會影響原陣列的
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
指向的是參照地址,修改屬性相當於是修改了參照地址中物件的屬性,也就會修改原陣列
綜上我們可以發現,如果要在forEach
中修改原陣列,那麼需要在其回撥函數中,通過索引index或者藉助Object.assgin()
才可以實現,最終原理都是修改參照地址中的資料,而不是直接修改。
非同步函數和同步函數的執行順序此處就不細說,詳情可以移步:搞不清楚事件迴圈,那就看看這篇文章,簡單來說就是同步程式碼先於非同步程式碼執行。
看一個例子:
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。
如果我們需要實現非同步求和,可以使用for迴圈實現:
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 })();
在使用for迴圈時,我們一般可使用break
、return
來跳出迴圈,丟擲異常也可以,但是這不是正常的開發流程。我們來試一下在forEach中使用break、return有沒有作用:
forEach結束迴圈
const arr = [1,2,3,4,5] let callbackCounts = 0; arr.forEach((item, index, array) => { callbackCounts++; if (item === 2) { return; // forEach中不能使用break,即使使用return,也無法中止迴圈 } }) console.log(arr.length, callbackCounts); // 5 5
如果非得要跳出forEach
迴圈,首先建議的是使用其他迴圈方法,例如:for、for of、for in、map等,其次我們可以考慮丟擲一個異常來跳出forEach
迴圈:
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
來丟擲異常,那麼使用其他迴圈方法不香嗎
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,則預設未window物件,此時的this.sum
為undefined,而我們想的是this指向傳入的陣列。那麼需要傳入this或者使用箭頭函數。
array.forEach((element) => { this.sum += element; }); array.forEach(function(element) { this.sum += element; }, this);
避免錯誤用法,當然就是正確用法咯。
其實forEach
在設計出來只是為了簡化for迴圈的遍歷,如果要過多的進行其他操作,就違背了設計初衷了。每一個API都有自己的適用範圍,如果堅持要一把梭,可能就會踩很多坑。
簡單總結一下正確的使用方法:
forEach()
的回撥函數使用箭頭函數,可避免this指向問題forEach
本身並不會改變原陣列,但是其回撥函數可能會修改。如果真要修改原陣列,建議使用map
、filter
等方法forEach
方法始終返回undefined,這使得forEach
無法像map
、filter
一樣可以鏈式呼叫forEach
無法被中止或者跳出迴圈,如果要跳出迴圈,建議使用其他for迴圈方法for await of
代替forEach
到此這篇關於JavaScript中forEach錯誤用法的文章就介紹到這了,更多相關js forEach錯誤用法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45