記錄--你不知道的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
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...