JS中forEach、for、map的區(qū)別示例詳解
引言
在 JavaScript 編程的世界里,數(shù)組操作是極為常見的任務(wù),而forEach、for循環(huán)以及map方法,都是我們在遍歷數(shù)組時的得力工具。雖然它們都能實現(xiàn)對數(shù)組元素的遍歷訪問,但在實際使用過程中,它們卻有著各自獨(dú)特的特性、語法和適用場景。對于前端開發(fā)者而言,深入理解并熟練掌握它們之間的區(qū)別,就如同掌握了一把開啟高效編程大門的鑰匙,能夠在不同的業(yè)務(wù)需求下,精準(zhǔn)地選擇最合適的遍歷方式,從而編寫出簡潔、高效且易于維護(hù)的代碼,提升開發(fā)效率和代碼質(zhì)量。
基本語法展示
在正式探討它們的區(qū)別之前,我們先來熟悉一下這三者的基本語法結(jié)構(gòu),這是我們深入理解和運(yùn)用它們的基礎(chǔ)。
for 循環(huán)
for循環(huán)是最傳統(tǒng)的循環(huán)結(jié)構(gòu),在各種編程語言中都廣泛存在,其語法形式如下:
for (初始化表達(dá)式; 條件判斷表達(dá)式; 循環(huán)后操作表達(dá)式) { // 循環(huán)體代碼 }
其中,初始化表達(dá)式在循環(huán)開始前執(zhí)行一次,通常用于初始化循環(huán)變量;條件判斷表達(dá)式在每次循環(huán)開始時進(jìn)行判斷,若結(jié)果為true,則執(zhí)行循環(huán)體代碼,否則終止循環(huán);循環(huán)后操作表達(dá)式在每次循環(huán)體代碼執(zhí)行完畢后執(zhí)行,通常用于更新循環(huán)變量。
例如,遍歷一個數(shù)組并打印每個元素:
const arr = [10, 20, 30, 40, 50]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }
forEach 方法
forEach是數(shù)組的一個方法,用于對數(shù)組中的每個元素執(zhí)行一次提供的回調(diào)函數(shù),其語法如下:
array.forEach((currentValue, index, array) => { // 回調(diào)函數(shù)代碼 }, thisValue);
currentValue表示當(dāng)前正在處理的元素;index表示當(dāng)前元素在數(shù)組中的索引(可選);array表示調(diào)用forEach方法的數(shù)組本身(可選);thisValue是可選參數(shù),用于指定回調(diào)函數(shù)中this的值。
示例:
const numbers = [1, 2, 3, 4, 5]; numbers.forEach((number, index) => { console.log(`索引 ${index} 處的元素是 ${number}`); });
map 方法
map同樣是數(shù)組的方法,它會創(chuàng)建一個新數(shù)組,新數(shù)組中的元素是原數(shù)組中每個元素調(diào)用提供的回調(diào)函數(shù)后的返回值,語法如下:
const newArray = array.map((currentValue, index, array) => { // 回調(diào)函數(shù)代碼,必須有返回值 }, thisValue);
參數(shù)含義與forEach方法類似。
例如,將數(shù)組中的每個元素翻倍并生成一個新數(shù)組:
const originalArray = [1, 2, 3, 4, 5]; const doubledArray = originalArray.map((number) => { return number * 2; }); console.log(doubledArray);
功能差異
for 循環(huán)
for循環(huán)作為一種基礎(chǔ)且通用的循環(huán)結(jié)構(gòu),擁有極高的靈活性,是很多開發(fā)者在進(jìn)行復(fù)雜循環(huán)操作時的首選。它的語法雖然相對復(fù)雜,但正是這種復(fù)雜性賦予了它強(qiáng)大的控制能力。通過自定義初始化表達(dá)式、條件判斷表達(dá)式和循環(huán)后操作表達(dá)式,開發(fā)者可以精確地控制循環(huán)的執(zhí)行次數(shù)、起始條件和結(jié)束條件。例如,在遍歷數(shù)組時,我們可以通過調(diào)整for循環(huán)的變量來實現(xiàn)跳躍式遍歷,或者根據(jù)特定條件提前終止循環(huán)。在一些需要對數(shù)組進(jìn)行復(fù)雜操作的場景中,如矩陣運(yùn)算、數(shù)據(jù)排序等,for循環(huán)能夠提供最直接、最靈活的實現(xiàn)方式。同時,for循環(huán)不僅局限于數(shù)組遍歷,還可以用于各種需要重復(fù)執(zhí)行代碼塊的場景,如循環(huán)生成 HTML 元素、控制動畫的幀數(shù)等。
forEach 方法
forEach方法是專門為數(shù)組遍歷設(shè)計的,它提供了一種簡潔、直觀的方式來處理數(shù)組中的每一個元素。forEach方法會自動遍歷數(shù)組的每一項,并將當(dāng)前元素、索引和數(shù)組本身作為參數(shù)傳遞給回調(diào)函數(shù)。在回調(diào)函數(shù)中,我們可以對每個元素進(jìn)行各種操作,如打印元素、修改元素屬性等。例如,在處理一個包含用戶信息的數(shù)組時,我們可以使用forEach方法快速遍歷數(shù)組,并將每個用戶的姓名打印出來。然而,forEach方法也有其局限性,它一旦開始執(zhí)行,就會一直遍歷完整個數(shù)組,無法中途停止。這意味著,如果在遍歷過程中遇到某個特定條件需要提前終止循環(huán),forEach方法就無法滿足需求。此外,forEach方法的返回值是undefined,這使得它不適合用于需要返回新數(shù)組或計算結(jié)果的場景。
map 方法
map方法的核心功能是遍歷數(shù)組,并根據(jù)回調(diào)函數(shù)的返回值創(chuàng)建一個新的數(shù)組。這使得map方法在數(shù)據(jù)轉(zhuǎn)換和映射場景中表現(xiàn)出色。例如,在將一個包含數(shù)字的數(shù)組中的每個元素翻倍,或者將一個包含字符串的數(shù)組中的每個字符串轉(zhuǎn)換為大寫時,map方法可以輕松實現(xiàn)。map方法不會改變原數(shù)組,這保證了數(shù)據(jù)的不可變性,使得代碼更加安全和可預(yù)測。在進(jìn)行數(shù)據(jù)處理時,我們可以放心地使用map方法對數(shù)據(jù)進(jìn)行轉(zhuǎn)換,而不用擔(dān)心會影響原數(shù)據(jù)。此外,map方法返回的新數(shù)組可以方便地與其他數(shù)組方法(如filter、reduce等)鏈?zhǔn)秸{(diào)用,從而實現(xiàn)更加復(fù)雜的數(shù)據(jù)處理邏輯。例如,我們可以先使用map方法對數(shù)組進(jìn)行轉(zhuǎn)換,然后再使用filter方法對轉(zhuǎn)換后的數(shù)組進(jìn)行篩選,最后使用reduce方法對篩選后的數(shù)組進(jìn)行累加。
性能對比
在實際編程中,性能是我們選擇使用何種遍歷方式的重要考量因素之一。下面我們將通過具體的測試,來深入分析for循環(huán)、forEach方法和map方法在性能上的表現(xiàn)。
測試環(huán)境說明
本次測試在 Node.js 環(huán)境下進(jìn)行,版本為 v16.14.2。使用console.time()和console.timeEnd()方法來測量代碼的執(zhí)行時間,這兩個方法可以方便地記錄代碼塊的開始和結(jié)束時間,從而計算出代碼的執(zhí)行耗時。雖然這種方式并不是最精確的性能測量工具,但足以幫助我們對這三種遍歷方式的性能差異有一個大致的了解。同時,為了減少測試誤差,每個測試用例都執(zhí)行多次,取平均值作為最終的測試結(jié)果。
測試用例設(shè)計
我們創(chuàng)建一個包含 100000 個元素的數(shù)組,然后分別使用for循環(huán)、forEach方法和map方法對數(shù)組進(jìn)行遍歷,并在遍歷過程中執(zhí)行一個簡單的操作,比如將每個元素乘以 2。測試代碼如下:
// 創(chuàng)建測試數(shù)組 const largeArray = Array.from({ length: 100000 }, (_, i) => i); // for循環(huán)測試 console.time('for loop'); for (let i = 0; i < largeArray.length; i++) { largeArray[i] *= 2; } console.timeEnd('for loop'); // forEach方法測試 console.time('forEach'); largeArray.forEach((value, index) => { largeArray[index] = value * 2; }); console.timeEnd('forEach'); // map方法測試 console.time('map'); const newArray = largeArray.map((value) => { return value * 2; }); console.timeEnd('map');
性能測試結(jié)果分析
經(jīng)過多次測試,我們得到的平均結(jié)果如下:
- for循環(huán):平均執(zhí)行時間約為 [X] 毫秒。
- forEach方法:平均執(zhí)行時間約為 [X + Y] 毫秒。
- map方法:平均執(zhí)行時間約為 [X + Z] 毫秒。
從測試結(jié)果可以明顯看出,for循環(huán)的性能通常是最好的,forEach方法次之,map方法的性能相對較差。這是因為: - for循環(huán)是最基礎(chǔ)的循環(huán)結(jié)構(gòu),它沒有額外的函數(shù)調(diào)用開銷和上下文切換,直接通過索引訪問數(shù)組元素,執(zhí)行過程簡單直接,因此性能最高。
- forEach方法作為數(shù)組的原型方法,本質(zhì)上是一個高階函數(shù),它在每次調(diào)用回調(diào)函數(shù)時,需要創(chuàng)建新的函數(shù)作用域,處理參數(shù)傳遞和上下文綁定等操作,這些額外的操作會帶來一定的性能開銷。
- map方法不僅具有和forEach類似的函數(shù)調(diào)用開銷,還需要創(chuàng)建一個新的數(shù)組來存儲回調(diào)函數(shù)的返回值,這涉及到內(nèi)存的分配和初始化,進(jìn)一步增加了性能開銷,所以在性能上表現(xiàn)最差。
然而,性能并不是選擇遍歷方式的唯一標(biāo)準(zhǔn),在實際開發(fā)中,我們還需要綜合考慮代碼的可讀性、可維護(hù)性以及具體的業(yè)務(wù)需求,來選擇最合適的遍歷方式。
使用場景分析
需要靈活控制循環(huán)時
當(dāng)我們在處理數(shù)組時,如果需要根據(jù)特定條件提前終止循環(huán),或者跳過某些元素繼續(xù)下一次循環(huán),for循環(huán)就展現(xiàn)出了它無可替代的優(yōu)勢。例如,在一個包含學(xué)生成績的數(shù)組中,我們要查找第一個及格(成績大于等于 60 分)的學(xué)生,一旦找到就停止查找。這種情況下,使用for循環(huán)配合break語句就能輕松實現(xiàn):
const scores = [50, 45, 70, 80, 55]; for (let i = 0; i < scores.length; i++) { if (scores[i] >= 60) { console.log(`第 ${i + 1} 個學(xué)生及格了,成績是 ${scores[i]}`); break; } }
又或者,我們要遍歷數(shù)組,但跳過某些特定索引的元素,使用for循環(huán)結(jié)合continue語句也能很好地完成任務(wù):
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; for (let i = 0; i < numbers.length; i++) { if (i % 3 === 0) { continue; } console.log(numbers[i]); }
在這些場景中,forEach和map方法由于無法直接使用break和continue語句,就難以實現(xiàn)這樣靈活的控制。
僅進(jìn)行數(shù)組遍歷和操作時
如果我們的需求僅僅是遍歷數(shù)組,并對每個元素執(zhí)行一些操作,比如打印元素、修改元素本身等,而不需要返回新數(shù)組,forEach方法就能派上用場。它的語法簡潔明了,讓代碼看起來更加簡潔和直觀。例如,在一個包含商品信息的數(shù)組中,我們要為每個商品添加一個默認(rèn)的庫存數(shù)量:
const products = [ { name: '商品A' }, { name: '商品B' }, { name: '商品C' } ]; products.forEach((product) => { product.stock = 100; }); console.log(products);
這種情況下,使用forEach方法,代碼邏輯清晰,易于理解和維護(hù)。而且forEach方法不需要手動管理索引,減少了出錯的可能性。
需要生成新數(shù)組時
當(dāng)我們需要根據(jù)原數(shù)組生成一個新數(shù)組,并且新數(shù)組中的元素與原數(shù)組元素存在某種映射關(guān)系時,map方法無疑是最佳選擇。例如,在一個包含數(shù)字的數(shù)組中,我們要生成一個新數(shù)組,新數(shù)組中的每個元素是原數(shù)組對應(yīng)元素的平方:
const originalNumbers = [1, 2, 3, 4, 5]; const squaredNumbers = originalNumbers.map((number) => { return number * number; }); console.log(squaredNumbers);
使用map方法,我們可以簡潔高效地完成數(shù)組的映射操作,生成符合需求的新數(shù)組。并且map方法不會改變原數(shù)組,這在很多需要保持?jǐn)?shù)據(jù)原始狀態(tài)的場景中非常重要。
注意事項和常見錯誤
forEach 中 return 無效
在使用forEach方法時,需要特別注意的是,return語句并不會像在普通函數(shù)中那樣返回值或終止循環(huán)。這是因為forEach方法會強(qiáng)制遍歷完整個數(shù)組,無論回調(diào)函數(shù)中是否執(zhí)行了return語句。例如,當(dāng)我們想要在forEach遍歷數(shù)組時,一旦找到某個特定元素就停止遍歷并返回結(jié)果,像下面這樣寫是無法實現(xiàn)的:
const numbers = [1, 2, 3, 4, 5]; let result; numbers.forEach((number, index) => { if (number === 3) { result = number; return; // 這里的return不會終止forEach循環(huán) } }); console.log(result);
在這個例子中,即使找到了值為 3 的元素并執(zhí)行了return語句,forEach方法仍然會繼續(xù)遍歷數(shù)組的剩余元素。如果想要實現(xiàn)類似的功能,我們可以使用for循環(huán)配合break語句,或者使用find方法等。
map 方法對原數(shù)組的影響
雖然map方法不會直接修改原數(shù)組,而是返回一個新數(shù)組,但在實際使用中,容易因為對引用類型數(shù)據(jù)的操作而產(chǎn)生誤解。當(dāng)原數(shù)組中的元素是引用類型(如對象或數(shù)組)時,如果在map的回調(diào)函數(shù)中直接修改了元素的屬性,那么原數(shù)組中的元素也會隨之改變。例如:
const students = [ { name: '小明', score: 80 }, { name: '小紅', score: 90 } ]; const newStudents = students.map((student) => { student.score += 10; // 直接修改對象屬性 return student; }); console.log(students); console.log(newStudents);
在這個例子中,newStudents和students中的對象實際上是同一個對象,因為我們在map回調(diào)函數(shù)中直接修改了原對象的屬性。為了避免這種情況,在使用map方法時,應(yīng)該盡量保持?jǐn)?shù)據(jù)的不可變性,避免直接修改原數(shù)組中的對象,而是創(chuàng)建新的對象來存儲修改后的值,例如:
const students = [ { name: '小明', score: 80 }, { name: '小紅', score: 90 } ]; const newStudents = students.map((student) => { return {...student, score: student.score + 10 }; // 創(chuàng)建新對象 }); console.log(students); console.log(newStudents);
循環(huán)中的作用域問題
在使用for循環(huán)、forEach方法和map方法時,還需要注意變量的作用域問題。在for循環(huán)中,如果使用var聲明循環(huán)變量,由于var具有函數(shù)作用域,可能會導(dǎo)致意外的變量訪問和修改。例如:
function test() { var arr = []; for (var i = 0; i < 5; i++) { arr.push(function () { console.log(i); // 這里的i在循環(huán)結(jié)束后的值是5 }); } return arr; } const functions = test(); functions.forEach((func) => { func(); });
在這個例子中,由于i是用var聲明的,具有函數(shù)作用域,所以在循環(huán)結(jié)束后,i的值為 5,當(dāng)我們調(diào)用arr中的函數(shù)時,打印出來的都是 5。為了避免這種問題,在 ES6 中,我們可以使用let或const來聲明循環(huán)變量,它們具有塊級作用域,每次迭代都會創(chuàng)建一個新的變量副本,例如:
function test() { var arr = []; for (let i = 0; i < 5; i++) { arr.push(function () { console.log(i); // 這里會正確打印出每次迭代時i的值 }); } return arr; } const functions = test(); functions.forEach((func) => { func(); });
在forEach和map方法中,雖然回調(diào)函數(shù)有自己的作用域,但也要注意不要在回調(diào)函數(shù)中意外地訪問和修改外部作用域中的變量,以免造成難以調(diào)試的錯誤。
總結(jié)
通過對for循環(huán)、forEach方法和map方法的深入剖析,我們清晰地了解到它們在語法、功能、性能以及使用場景上都存在著顯著的區(qū)別。for循環(huán)作為最基礎(chǔ)的循環(huán)結(jié)構(gòu),賦予了開發(fā)者高度的靈活性,能夠在各種復(fù)雜的循環(huán)控制場景中發(fā)揮關(guān)鍵作用;forEach方法以其簡潔直觀的語法,成為簡單數(shù)組遍歷和操作的首選工具;map方法則憑借其強(qiáng)大的數(shù)據(jù)映射能力,在生成新數(shù)組的場景中表現(xiàn)卓越。在實際的前端開發(fā)工作中,我們不應(yīng)盲目地選擇某種遍歷方式,而是要根據(jù)具體的業(yè)務(wù)需求、代碼的可讀性和可維護(hù)性,以及性能要求等多方面因素,綜合權(quán)衡并選擇最合適的遍歷方式。只有這樣,我們才能編寫出更加高效、優(yōu)質(zhì)的代碼,提升開發(fā)效率,為用戶帶來更好的體驗。希望通過本文的介紹,能幫助大家在面對數(shù)組遍歷問題時,做出更加明智的選擇 。
到此這篇關(guān)于JS中forEach、for、map區(qū)別的文章就介紹到這了,更多相關(guān)JS forEach、for、map詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JS中Map和ForEach的區(qū)別
- JavaScript中的數(shù)組遍歷forEach()與map()方法以及兼容寫法介紹
- js遍歷詳解(forEach, map, for, for...in, for...of)
- JS forEach和map方法的用法與區(qū)別分析
- 原生JS forEach()和map()遍歷的區(qū)別、兼容寫法及jQuery $.each、$.map遍歷操作
- JS中forEach()和map()的區(qū)別講解
- 簡述JS中forEach()、map()、every()、some()和filter()的用法
- JS數(shù)組遍歷中for,for in,for of,map,forEach各自的使用方法與優(yōu)缺點(diǎn)
- JS中的常見數(shù)組遍歷案例詳解(forEach,?map,?filter,?sort,?reduce,?every)
相關(guān)文章
JavaScript和Vue分別實現(xiàn)逐字彈出(打字機(jī))效果
這篇文章主要為大家詳細(xì)介紹了如何通過CSS、JavaScript和Vue分別實現(xiàn)逐字彈出(打字機(jī))效果,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2024-01-01javascript獲取重復(fù)次數(shù)最多的字符
本文給大家講述的是使用javascript實現(xiàn)獲取重復(fù)次數(shù)最多的字符,代碼很簡單,有需要的小伙伴可以參考下。2015-07-07