JavaScript作為Web前端開發的基石,其強大的功能和靈活性不僅體現在網頁的動態交互上,更在于其處理數據的能力。數組遍歷是JavaScript中最常見的操作之一,尤其在算法題的求解過程中,它扮演著至關重要的角色。本文將深入探討JavaScript中數組遍歷的多種方法,通過具體的算法題示例,幫助讀者掌握高效解決問題的技巧。
在JavaScript中,數組遍歷可以通過多種方式進行,每種方法都有其特點和適用場景:
const numbers = [1, 2, 3, 4, 5];
// 使用for循環遍歷
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
// 使用forEach遍歷
numbers.forEach(number => console.log(number));
// 使用map創建新數組
const doubled = numbers.map(number => number * 2);
console.log(doubled); // 輸出: [2, 4, 6, 8, 10]
數組遍歷方法本質上是通過迭代數組中的每一個元素來執行特定的邏輯操作。不同的方法提供不同的操作能力,如map用于變換,filter用于篩選,而reduce用于聚合。
假設我們有一道算法題,要求找出數組中所有偶數,并返回它們的平方和。
function sumOfSquaresEvenNumbers(numbers) {
return numbers
.filter(number => number % 2 === 0) // 篩選偶數
.map(number => number * number) // 平方
.reduce((acc, curr) => acc + curr, 0); // 求和
}
const result = sumOfSquaresEvenNumbers([1, 2, 3, 4, 5, 6]);
console.log(result); // 輸出: 56
function optimizedSumOfSquaresEvenNumbers(numbers) {
let sum = 0;
for (let number of numbers) {
if (number % 2 === 0) {
sum += number * number;
}
}
return sum;
}
const optimizedResult = optimizedSumOfSquaresEvenNumbers([1, 2, 3, 4, 5, 6]);
console.log(optimizedResult); // 輸出: 56
數組遍歷不僅是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.');
這樣即可在遍歷中訪問數組的索引值與數組元素,又可以極為方便地隨時終止遍歷。
隨著ES6中新增對數組的find方法,對于數組的遍歷已經有越來越多的方法可以選擇,我們完全可以拋棄古老的for方法。今天這篇文章我們就一起來看看在Javascript中都可以使用哪些方法來遍歷一個數組吧,同時提供一些兼容性的處理。
本篇文章中的代碼已經開源到Github上,感興趣的可以自取,Github地址為:
https://github.com/zhouxiongking/article-pages/blob/master/articles/arrayTraverse/arrayTraverse.js
Javascript
forEach是數組遍歷使用最為頻繁的一個方法,它的作用是使用定義的函數處理數組中的每個元素。
首先我們來看看forEach的基本使用方法。
forEach基本使用
forEach中定義的回調函數接收三個參數,分別是當前元素值,當前元素索引,當前數組。
實際上forEach方法接收第二個參數,如果傳入這個參數,則回調函數中的this就指向這個參數值,如果沒有傳入,則this指向全局變量window。通過下面的代碼可以很容易看清楚。
傳入第二個參數
這里我們提供一個兼容性處理的方案。
forEach兼容性處理
map方法會將數組中每個元素做處理得到新的元素,然后返回這些新的元素組成的數組。其回調函數中接收的參數和forEach一樣,其基本使用如下。
map基本使用
需要注意的一點是,在map的回調函數中需要有return返回值,如果沒有返回值,得到的結果會是由undefined組成的數組。
必須有返回值
這里提供一下兼容性處理。
map兼容性處理
filter顧名思義,過濾數組中滿足條件的數值,得到一個新的數組。在filter的回調函數中需要返回true或者false,true代表滿足條件,通過篩選;false代表不滿足條件,不通過篩選。
filter基本使用方法
這里提供一下兼容性處理。
filter兼容性處理
some方法與every方法在使用上有很多相似之處。some方法的作用是只要數組中某個元素滿足條件就返回true;而every方法作用是數組中每個元素都要滿足條件才返回true。
它們的測試結果如下所示。
some與every
some與every方法的兼容性處理也很類似,一個是對true值的判斷,一個是對false值的判斷。對some方法的兼容性處理如下。
some方法兼容性處理
every方法兼容性處理如下。
every兼容性處理
reduce方法作用是使用可能提供的初始值來處理數組中的每個元素,每輪計算都會有一個返回值進入下一輪計算,得到一個最終的返回值。
reduce在是否接收初始值上會對結果有很大的影響。在不接收初始值的情況下,會將第一個值作為初始值,索引是從第二個值開始計算。通過下面的代碼可以很容易看出來。
reduce不接收初始值
當reduce接收初始值時,索引是從數組第一個值開始。
reduce接收初始值
這里提供對reduce方法的兼容性處理。
reduce方法兼容性處理
find方法是ES6新增的方法,其作用是返回數組中第一個滿足條件的值,如果都不滿足條件則返回undefined。其基本使用方法如下所示。
find基本使用
這里提供一下兼容性處理。
find兼容性處理
今天這篇文章主要總結了下Javascript中對數組進行遍歷的一些方法,以及對低級瀏覽器提供了一些兼容性處理,你學會了嗎?
*請認真填寫需求信息,我們會在24小時內與您取得聯系。