記錄--你不知道的forEach函數

来源:https://www.cnblogs.com/smileZAZ/archive/2023/03/14/17215892.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 老實說我不喜歡用forEach,因為它導致的一些bug總是這麼不經意,盤點我不喜歡的原因 原因一:不支持處理非同步函數 先看一個例子: async function test() { let arr = [3, 2, 1] arr.forE ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

老實說我不喜歡用forEach,因為它導致的一些bug總是這麼不經意,盤點我不喜歡的原因

原因一:不支持處理非同步函數

先看一個例子:

async function test() {
    let arr = [3, 2, 1]
    arr.forEach(async item => {
        const res = await mockSync(item)
        console.log(res)
    })
    console.log('end')
}

function mockSync(x) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
                resolve(x)
        }, 1000 * x)
    })
}
test()
我們期望的結果是:
3
2 
1
end

但是實際上會輸出:

end
1
2
3

JavaScript中的forEach()方法是一個同步方法,它不支持處理非同步函數。如果你在forEach中執行了非同步函數,forEach()無法等待非同步函數完成,它會繼續執行下一項。這意味著如果在forEach()中使用非同步函數,無法保證非同步任務的執行順序。

替代forEach的方式

1.方式一

可以使用例如map()filter()reduce()等,它們支持在函數中返回Promise,並且會等待所有Promise完成。

使用map()Promise.all()來處理非同步函數的示例代碼如下:

const arr = [1, 2, 3, 4, 5];

async function asyncFunction(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  });
}

const promises = arr.map(async (num) => {
  const result = await asyncFunction(num);
  return result;
});

Promise.all(promises).then((results) => {
  console.log(results); // [2, 4, 6, 8, 10]
});

由於我們在非同步函數中使用了await關鍵字,map()方法會等待非同步函數完成並返回結果,因此我們可以正確地處理非同步函數。

方式二 使用for迴圈來處理非同步函數

const arr = [1, 2, 3, 4, 5];

async function asyncFunction(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  });
}

async function processArray() {
  const results = [];
  for (let i = 0; i < arr.length; i++) {
    const result = await asyncFunction(arr[i]);
    results.push(result);
  }
  console.log(results); // [2, 4, 6, 8, 10]
}

processArray();

原因二:無法捕獲非同步函數中的錯誤

如果非同步函數在執行時拋出錯誤,forEach()無法捕獲該錯誤。這意味著即使在非同步函數中出現錯誤,forEach()仍會繼續執行。

原因三:除了拋出異常以外,沒有辦法中止或跳出 forEach() 迴圈

forEach()方法不支持使用breakcontinue語句來跳出迴圈或跳過某一項。如果需要跳出迴圈或跳過某一項,應該使用for迴圈或其他支持breakcontinue語句的方法。

原因四:forEach 刪除自身元素,index不可被重置

forEach中我們無法控制 index 的值,它只會無腦的自增直至大於數組的 length 跳出迴圈。所以也無法刪除自身進行index重置,先看一個簡單例子:

let arr = [1,2,3,4]
arr.forEach((item, index) => {
    console.log(item); // 1 2 3 4
    index++;
});

原因五:this指向問題

forEach()方法中,this關鍵字引用的是調用該方法的對象。但是,在使用普通函數或箭頭函數作為參數時,this關鍵字的作用域可能會出現問題。在箭頭函數中,this關鍵字引用的是定義該函數時所在的對象。在普通函數中,this關鍵字引用的是調用該函數的對象。如果需要確保this關鍵字的作用域正確,可以使用bind()方法來綁定函數的作用域。以下是一個關於forEach()方法中this關鍵字作用域問題的例子:

const obj = {
  name: "Alice",
  friends: ["Bob", "Charlie", "Dave"],
  printFriends: function () {
    this.friends.forEach(function (friend) {
      console.log(this.name + " is friends with " + friend);
    });
  },
};
obj.printFriends();
在這個例子中,我們定義了一個名為obj的對象,它有一個printFriends()方法。在printFriends()方法中,我們使用forEach()方法遍歷friends數組,並使用普通函數列印每個朋友的名字和obj對象的name屬性。但是,當我們運行這個代碼時,會發現輸出結果為:
undefined is friends with Bob
undefined is friends with Charlie
undefined is friends with Dave

這是因為,在forEach()方法中使用普通函數時,該函數的作用域並不是調用printFriends()方法的對象,而是全局作用域。因此,在該函數中無法訪問obj對象的屬性。

為瞭解決這個問題,可以使用bind()方法來綁定函數的作用域,或使用箭頭函數來定義回調函數。以下是使用bind()方法解決問題的代碼示例:

const obj = {
  name: "Alice",
  friends: ["Bob", "Charlie", "Dave"],
  printFriends: function () {
    this.friends.forEach(
      function (friend) {
        console.log(this.name + " is friends with " + friend);
      }.bind(this) // 使用bind()方法綁定函數的作用域
    );
  },
};
obj.printFriends();

在這個例子中,我們使用bind()方法來綁定函數的作用域,將該函數的作用域綁定到obj對象上。運行代碼後,輸出結果為:

Alice is friends with Bob 
Alice is friends with Charlie 
Alice is friends with Dave

通過使用bind()方法來綁定函數的作用域,我們可以正確地訪問obj對象的屬性。

另一種解決方法是使用箭頭函數。由於箭頭函數沒有自己的this,它會繼承它所在作用域的this。因此,在箭頭函數中,this關鍵字引用的是定義該函數時所在的對象。代碼略

原因六: forEach性能比for迴圈低

for:for迴圈沒有額外的函數調用棧和上下文,所以它的實現最為簡單。
forEach:對於forEach來說,它的函數簽名中包含了參數和上下文,所以性能會低於 for 迴圈。

原因七:會跳過已刪除或者未初始化的項

// 跳過未初始化的值
const array = [1, 2, /* empty */, 4];
let num = 0;

array.forEach((ele) => {
  console.log(ele);
  num++;
});

console.log("num:",num);

//  1
//  2 
//  4 
// num: 3

// 跳過已刪除的值
const words = ['one', 'two', 'three', 'four'];
words.forEach((word) => {
  console.log(word);
  if (word === 'two') {
  // 當到達包含值 `two` 的項時,整個數組的第一個項被移除了
  // 這導致所有剩下的項上移一個位置。因為元素 `four` 正位於在數組更前的位置,所以 `three` 會被跳過。
    words.shift(); //'one' 將從數組中刪除
  }
}); // one // two // four

console.log(words); // ['two', 'three', 'four']

原因八:forEach使用不會改變原數組

forEach() 被調用時,不會改變原數組,也就是調用它的數組。但是那個對象可能會被傳入的回調函數改變

// 例子一
const array = [1, 2, 3, 4]; 
array.forEach(ele => { ele = ele * 3 }) 
console.log(array); // [1,2,3,4]

// 解決方式,改變原數組
const numArr = [33,4,55];
numArr.forEach((ele, index, arr) => {
    if (ele === 33) {
        arr[index] = 999
    }
})
console.log(numArr);  // [999, 4, 55]

// 例子二
const changeItemArr = [{
    name: 'wxw',
    age: 22
}, {
    name: 'wxw2',
    age: 33
}]
changeItemArr.forEach(ele => {
    if (ele.name === 'wxw2') {
        ele = {
            name: 'change',
            age: 77
        }
    }
})
console.log(changeItemArr); // [{name: "wxw", age: 22},{name: "wxw2", age: 33}]

// 解決方式
const allChangeArr = [{    name: 'wxw',    age: 22}, {    name: 'wxw2',    age: 33}]
allChangeArr.forEach((ele, index, arr) => {
    if (ele.name === 'wxw2') {
        arr[index] = {
            name: 'change',
            age: 77
        }
    }
})
console.log(allChangeArr); // // [{name: "wxw", age: 22},{name: "change", age: 77}]

好了,我還是使用for...of去替代forEach

本文轉載於:

https://juejin.cn/post/7207411012487381051

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL鎖的粒度分為:行級鎖、表級鎖、頁級鎖。 一、行級鎖(INNODB引擎) 行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。 行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。 行級鎖分為共用鎖 和 排他鎖。 特點:開銷大,加鎖慢;會出現死鎖;鎖定 ...
  • 轉載自作者zhang502219048的微信公眾號【SQL資料庫編程】:巧妙使用SQL Server的計算列實現項目唯一規則快速定製 軟體產品,相當於是一個通用模板。而軟體項目,則是基於軟體產品的項目個性化定製。不同軟體項目的定製多種多樣,如何能快速實現軟體項目的定製,則是軟體產品設計者所需要優先考 ...
  • 前言 前面我們簡單的瞭解了 vue 初始化時的一些大概的流程,這裡我們詳細的瞭解下具體的內容;這塊建議搭建可以根據 demo 進行 debugger 來觀察; 內容 這一塊主要圍繞init.ts中的initLifecycle進行剖析。 initLifecycle initLifecycle的方法位於 ...
  • 分享一個面試題: 聲明一個數組,代表股票的各個期值,求在這個階段最大的收益值為多少? 簡言之:其實就是求數組中兩個值的差值中,最大的值。 第一反應的思路就是,進行雙層迴圈進行差值計算,再從差值計算獲得的數組中選出最大的值。面試完想了這個方法一下有些麻煩,還不如直接就在迴圈中比較出來,選出最大值,直接 ...
  • OSI OSI是Open System Interconnect的縮寫,意為開放式系統互聯。其各個層次的劃分遵循下列原則: ​ (1)同一層中的各網路節點都有相同的層次結構,具有同樣的功能。 ​ (2)同一節點內相鄰層之間通過介面進行通信。 ​ (3)七層結構中的每一層使用下一層提供的服務,並且向其 ...
  • 前言 前面我們簡單的瞭解了 vue 初始化時的一些大概的流程,這裡我們詳細的瞭解下具體的內容; 這塊建議搭建可以根據 demo 進行 debugger 來觀察; 內容 這一塊主要圍繞init.ts中的mergeOptions進行剖析。 mergeOptions mergeOptions的方法位於sc ...
  • 其他章節請看: webgl 系列 漸變三角形 本文通過一個漸變三角形的示例逐步分析:varying變數、合併緩衝區、圖形裝配、光柵化、varying 內插 繪製三個點v1 需求:繪製三個相同顏色的點,效果如下: 通過三角形的學習,這個需求非常容易實現。代碼如下: const VSHADER_SOUR ...
  • 情景 關鍵 組件沒有正確引入 函數無限遞歸 解決 如果在網上搜索[Vue warn]: Component is missing template or render function. 或[Vue warn]: Invalid vnode type when creating vnode: nul ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...