JavaScript 作為 Web 開發的核心語言之一,在前端領域發揮著至關重要的作用。數組作為 JavaScript 中最常用的數據結構之一,掌握其遍歷方法對于任何前端開發者都是必不可少的技能。本文旨在介紹幾種常見的數組遍歷方式,并通過實例演示它們的應用場景和最佳實踐。
數組遍歷是指按照一定的順序訪問數組中的每一個元素的過程。JavaScript 提供了多種方法來遍歷數組,包括傳統的 for 循環、forEach 方法、map、filter 等高階函數。
const numbers = [1, 2, 3, 4, 5];
// 使用 for 循環
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
// 使用 forEach
numbers.forEach(function(number) {
console.log(number);
});
// 使用 map
const doubled = numbers.map(function(number) {
return number * 2;
});
console.log(doubled);
// 使用 filter
const evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
});
console.log(evenNumbers);
數組遍歷的基本原理是按照一定的順序訪問數組中的每個元素。不同的遍歷方法內部實現有所不同,但最終目的是相同的。
假設我們需要從一個數組中找出所有的偶數,并計算這些偶數的平方。
問題: 如何高效地找到所有偶數并計算它們的平方?
解決方案: 使用 filter 方法篩選出偶數,再使用 map 方法計算平方。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 使用 filter 和 map
const evenSquares = numbers
.filter(number => number % 2 === 0)
.map(number => number * number);
console.log(evenSquares); // 輸出: [4, 16, 36, 64, 100]
// 緩存結果
const cache = {};
function getEvenSquares(numbers) {
if (cache[numbers]) {
return cache[numbers];
}
const result = numbers
.filter(number => number % 2 === 0)
.map(number => number * number);
cache[numbers] = result;
return result;
}
問題: 如何在 forEach 中中斷循環?
解決方案: 使用 for 循環或 Array.prototype.some() 方法。
// 使用 some
const numbers = [1, 2, 3, 4, 5];
let found = false;
numbers.some(function(number) {
if (number > 3) {
found = true;
return true; // 終止循環
}
});
console.log(found); // 輸出: true
通過本文的學習,我們了解了 JavaScript 數組遍歷的多種方法及其應用場景。每種方法都有其特點和適用場合,合理選擇可以提高代碼的效率和可讀性。隨著 JavaScript 語言的發展,新的遍歷方法和優化技巧會不斷出現,我們期待未來能有更多實用高效的工具和技術出現,以進一步提升前端開發的效率和質量。
希望本文能為你在實際開發中遇到的問題提供一些解決方案和思路,同時也鼓勵你不斷學習新的技術和方法,以應對日益復雜的前端開發挑戰。
、for...in 語句
1.1 遍歷對象屬性名稱
for...in 語句常用于遍歷特定對象的屬性,包括字段名稱屬性及函數名稱屬性。在JavaScript語言中,它們均稱為屬性 (property)。
let obj = {
name: 'obj',
showName() {
console.log(this.name);
}
};
for (const propName in obj) {
console.log(propName, typeof(obj[propName]));
}
顯示:
name – "string"
showName – "function"
利用這一點,可方便地查看特定對象的所有屬性或方法名稱。下面語句打印出console對象所有的屬性:
for (const propName in console) {
console.log(propName, typeof(console[propName]));
}
顯示:
debug – "function"
error – "function"
log – "function"
info – "function"
warn – "function"
clear – "function"
...
可以看出,這些屬性全部均是console的函數名稱,因為console沒有屬性名稱。
1.2 遍歷數組索引值
for...in 用于數組中,則遍歷數組索引值。
let array = ['a', 'b', 'c'];
for (const index in array) {
console.log(index, array[index]);
}
顯示:
0 – "a"
1 – "b"
2 – "c"
1.3 遍歷字符串索引值
由于字符串是由字符組成的數組,因此 for...in 也可用于遍歷字符串的索引值。
let str = "abc";
for (const index in str) {
console.log(index, str[index]);
}
顯示:
0 – "a"
1 – "b"
2 – "c"
2、for...of 語句
for...of 語句用于遍歷可遍歷對象 (iterable objects)的元素。這些可遍歷對象包括字符串、數組、以及類似于數組的對象,這些對象都帶有length屬性。
2.1 不能用于遍歷對象屬性名稱
for...of 語句不能用于遍歷對象的屬性名稱。因此,下面的代碼是錯誤的:
let obj = {
name: 'obj',
showName() {
console.log(this.name);
}
};
for (const propName of obj) {
console.log(propName);
}
顯示:
TypeError: undefined is not a function (near '...propName of obj...')
意為,將 for...of 語句用于對象上面,無法提取具體的數值。
2.2 遍歷數組元素
for...of 語句經常用于遍歷數組中各元素的數值。
let arr = [2, 4, 6, 8, 10];
for (const value of arr) {
console.log(value);
}
顯示:
2
4
6
8
10
2.3 遍歷字符串中的字符
由于字符串是由字符組成的數組,因此 for...of 也可用于遍歷字符串的字符。
let str = "abc";
for (const letter of str) {
console.log(letter);
}
顯示:
a
b
c
2.4 解包
數組元素如果是帶有特定屬性名稱的對象,可利用解包性質來快速遍歷這些屬性值。看下面例子。
function Point(x, y) {
return {x:x, y:y};
}
let points = [Point(1, 2), Point(2, 3), Point(4, 5)];
for (const point of points) {
console.log(point.x, point.y);
}
可將Point視為一個構造器 (constructor),每次調用Point(x, y)都會創建并返回該類的一個對象,且含有x及y的屬性名稱。points則是一個含有多個Point對象的數組。上面的代碼遍歷出每個Point對象后,賦值于point變量,然后打印出它們的x值及y值。
如果我們不希望每次都通過引用對象屬性的方式來訪問x及y值,則可編寫代碼如下:
for (const point of points) {
let x = point.x;
let y = point.y;
console.log(x, y);
}
這一步可利用ES6的const解包特性予以簡化:
for (const point of points) {
const {x, y} = point;
console.log(x, y);
}
更進一步,我們可以直接解包:
for (const {x, y} of points) {
console.log(x, y);
}
2.5 遍歷Map
let scoreMap = new Map([
['Mike', 75],
['Tom', 80],
['Smith', 90]
]);
for (const [key, value] of scoreMap) {
console.log(key, value);
}
與上一節不同的是,Map需要使用 [key, value] 的方式來解包。
3、forEach 方法
3.1 forEach 常見調用方式
for...in,for...of 均是語句,與它們不同的是,forEach是數組的內嵌方法。這意味著我們可以直接在數組對象上面直接調用該方法。
let arr = [1, 3, 5, 7, 9];
arr.forEach((element) => {
console.log(element);
});
作為數組方法,forEach有一個參數,該參數的類型是函數,稱為回調函數 (callback function)。所謂回調函數,是指一旦程序員提供了這樣的函數,JavaScript引擎將負責調用此函數。
回調函數的價值在于回調函數可能存在多個參數,而這些參數將由JavaScript引擎自動提供。在回調函數中,我們可對JavaScript引擎所自動提供的參數進行進一步加工。
在上面的回調函數中,element是由JavaScript引擎自動提供的,代表每個數組元素。
上面的代碼采用了lambda匿名表達式。它等同于:
let arr = [1, 3, 5, 7, 9];
function callback(element) {
console.log(element);
}
arr.forEach(callback);
可見,lambda表達式更加簡練。
3.2 forEach 的參數
forEach共有3個參數 (上面例子只用了第1個),它們的排列順序如下:
arr.forEach((element, index, array) => {
console.log(element);
console.log(index);
console.log(array);
});
參數element是數組元素,參數index是數組元素所在數組中的索引值,參數array是整個數組。
一般情況下,我們僅需用到element及index參數就足夠了。由于是每次迭代,因此,forEach方法中的array參數較少用到。
index每次遍歷時都會加1,且每次都會與array的長度比較。一旦超出array的界限,就會終止遍歷。如果遍歷過程中,修改了array的長度,則需特別注意這一點。
3.2 forEach 遍歷的終止
如何中止forEach的遍歷?JavaScript并未提供這樣的機制,但我們可以用一個雙重嵌套的異常來終止遍歷。
let arr = [1, 3, 5, 7, 9];
try {
arr.forEach((element, index, array) => {
try {
console.log(index);
if (index >= 3) {
throw new Error('forEach termination signal');
}
} catch (error) {
throw error;
}
});
} catch (e) {
if (e.message === 'forEach termination signal') {
console.log('forEach terminated.');
}
}
console.log('This line of code should be executed.');
顯示:
0
1
2
3
forEach terminated.
This line of code should be executed.
我們設定,當index的值大于等于3時,需要終止遍歷。這樣,在第7行,當此條件滿足時,即拋出"forEach termination signal"的異常。
此時,程序流程轉入到第10行至第12行最內層的異常捕獲處理代碼:
} catch (error) {
throw error;
}
捕獲異常后,如果我們不重新拋出異常,JavaScript引擎則會認為我們已正確地處理了異常,因此會恢復中斷的遍歷進程,繼續處理下一個數組元素,這不是我們想要的。因此,我們在此重新拋出該異常,以切實終止遍歷。
這時,forEach的遍歷因異常而終止,從而達到了我們的最初的目標。但因為有異常,如果我們未作任何處理,則該異常會導致整個程序都終止運行。只有在我們處理了異常后,程序才能往下走。這就是第14行至18行最外層異常捕獲代碼的作用:
} catch (e) {
if (e.message === 'forEach termination signal') {
console.log('forEach terminated');
}
}
先判斷它是不是"forEach termination signal"。如果是,則簡單地打印一行消息。由于這里未再拋出新的異常,因此JavaScript引擎認為我們已經正確地處理了異常,則繼續執行后面的代碼。這樣,最后一行第20行語句將被執行并打印出"This line of code should be executed."的文本。
一般來講,如果我們需要在數組的遍歷過程中終止遍歷,不要使用 forEach 語句,使用最傳統的方式即可:
let arr = [1, 3, 5, 7, 9];
for (let i = 0; i < arr.length; i++) {
console.log(i, arr[i]);
if (i >= 3) {
break;
}
}
console.log('This line of code should be executed.');
這樣即可在遍歷中訪問數組的索引值與數組元素,又可以極為方便地隨時終止遍歷。
歷數組是任何一門語言必不可少的功能,本文主要介紹JavaScript中遍歷數組的各種方法。
首先定義一個數組,如下圖所示:
arr.every(callback(currentValue, index, array)[, thisArg])
測試數組的所有元素是否都通過了指定函數的測試
callback 用來測試每個元素的函數。
thisArg 可選,如果為 every 提供一個 thisArg 參數,在該參數為調用 callback 時的 this 值。如果省略該參數,則 callback 被調用時的 this 值,在非嚴格模式下為全局對象,在嚴格模式下傳入 undefined。
currentValue:元素值,index:元素的索引,array:原數組
every 不會改變原數組。當callback函數返回false時,循環結束,示例如下:
打印結果如下:
arr.some(callback(currentValue, index, array)[, thisArg])
currentValue:元素值,index:元素的索引,array:原數組
some 不會改變原數組。與every不通的是,當callback函數返回true時,循環結束,示例如下:
打印結果如下:
arr.forEach(callback(currentValue, index, array)[, thisArg])
為每個元素都執行callback函數,直接修改原數組,沒有返回值,循環過程中不能停止,示例如下:
打印結果如下:
arr.map(callback (currentValue, index, array)[, thisArg])
返回一個經過處理的新數組,不會破壞原來的數組,需要注意的是callback函數里面需要返回currentValue,也就是說map是通過每次循環返回的值作為元素組成新數組的,并且在循環過程中不能停止,示例如下:
打印結果如下:
arr.filter(callback (element, index, arrArg)[, thisArg])
對數組中的每個元素都執行一次指定的函數(callback),并且創建一個新的數組,該數組元素是所有回調函數執行時返回值為 true 的原數組元素。示例如下:
打印結果如下:
arr.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
方法對累加器和數組中的每個元素 (從左到右)應用一個函數,將其減少為單個值,返回函數累計處理的結果。
參數說明:
callback:執行數組中每個值的函數,包含四個參數
accumulator 上一次調用回調返回的值,或者是提供的初始值(initialValue)
currentValue 數組中正在處理的元素
currentIndex 數據中正在處理的元素索引,如果提供了 initialValue ,從0開始;否則從1開始
initialValue 可選項,其值用于第一次調用 callback 的第一個參數。如果沒有設置初始值,則將數組中的第一個元素作為初始值。空數組調用reduce時沒有設置初始值將會報錯。示例如下:
打印結果如下:
for循環是使用頻率較高的方法,效率也較高。數據量較大時,建議用此種方法。它可以通過break語句跳出循環,也可以通過continue語句跳過當前循環,示例如下:
打印結果如下:
需要注意的是for循環里面不支持return語句,如下寫法會報錯:
打印結果如下:
for方法還有兩種變體如下,但是效率不高:
這種方式效率最低
需要ES6支持,性能要好于forin,但仍然比不上普通for循環
js數組遍歷函數雖然比較多,但各有各的用途,還希望大家在寫項目代碼的過程中多多嘗試一下。如果不知選哪個,或者比較注重效率的話,最佳的選擇還是普通for循環。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。