前言
最近看了好幾篇總結數組中遍歷方法的文章,然而“紙上得來終覺淺”,決定此事自己干。於是小小總結,算是自己練手了。
各種數組遍歷方法
數組中常用的遍歷方法有四種,分別是:
- for
- for-in
- forEach
- for-of (ES6)
for
使用for循環來遍歷一個數組,代碼如下:
// for循環
let arr=[2,4,6,10];
for(let i=0;i<arr.length;i++){
console.log(arr[i]);
}
當數組的長度不回改變時,我們使用一個變量來存儲數組的長度arr.length
,以獲得更好的效率(因為每次比較的時候都省去計算arr.length
)。改進后的算法如下:
// for循環
let arr=[2,4,6,10];
for(let i=0,len=arr.length;i<len;i++){
console.log(arr[i]);
}
當然如果不考慮輸出順序,為了簡化,也可以使用i--
,代碼如下:
// for循環
let arr=[2,4,6,10];
for(let i=arr.length;i>=0;i--){
console.log(arr[i]);
}
for-in
通常情況下,我們可以這樣使用:
//for-in 循環
let arr=[2,4,6,10];
let index;
for (index in arr){
console.log(arr[index]);
}
輸出結果
arr[0]=2
arr[1]=4
arr[2]=6
arr[3]=10
來,我們再看一個例子:
//for-in 循環
let per={
name:"zhang San",
sex:'male',
age:18
};
for(let key in per){
console.log("per[" + key + "]=" + per[key]);
}
輸出結果
per[name]=zhang San
per[sex]=male
per[age]=18
根據上面的兩個例子可以看到,for-in
不僅可以遍歷數組
還可以遍歷對象
。
注意:
文章深入了解 JavaScript 中的 for 循環 指出for-in 遍歷屬性的順序並不確定,即輸出的結果順序與屬性在對象中的順序無關,也與屬性的字母順序無關,與其他任何順序也無關。,但是我在實際操作中,發現輸出一直是上面的結果,即其按照了定義屬性的順序輸出了。
此時,我們回頭再次看一個for-in的例子:
Array.prototype.fatherName = "Father";
let arr=[2,4,6,10];
arr.name="hello,world!";
let index;
for (index in arr){
console.log('arr['+index+']='+arr[index]);
}
輸出結果
arr[0]=2
arr[1]=4
arr[2]=6
arr[3]=10
arr[name]=hello,world!
arr[Father]=Father
我們發現一個問題,使用for-in
在遍歷的時候,它不僅遍歷了對象上的屬性,而且還遍歷了對象父類原型上的屬性。
所以for-in
並不適合遍歷Array中的元素,更適合遍歷對象中的屬性,這也是創造for-in的初衷。但是對於稀疏數組
,使用它卻是極好的。例子如下:
let key;
let arr = [];
arr[0] = "a";
arr[999] = "b";
arr[99999] = "c";
for(key in arr) {
if(arr.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 100000) {
console.log(arr[key]);
}
}
在上面的例子當中,由於for-in
只會遍歷存在的實體,因此使用for-in
循環,只需要遍歷3次,而使用for
循環則需要遍歷100000次。
for-in的性能
由於使用它的時候,每次需要遍歷對象中存在的實體,以及對應的原型鏈上的屬性。因此其速度相比較其他for循環,要慢一些。所以除非明確要迭代一個
屬性數量未知
的對象,否則應該避免使用for-in
。
forEach
forEach()
方法為數組中的每一個有效元素執行一次callback函數。遍歷數組讓數組中的每一個元素做一件事情。那些已經被刪除(使用delete方法等情況)或者未初始化的項將被跳過(但不包含那些值未undefined的項目)。
注意,callback函數將依次被傳入3個參數:
- 數組當前項的值
- 數組當前項的索引
- 數組對象本身
看個例子:
let arr = [];
arr[0] = "a";
arr[3] = "b";
arr[10] = "c";
arr[name] = "Hello world";
arr.forEach((data, index, array) => {
console.log(data, index, array);
});
輸出結果
a 0 ["a", 3: "b", 10: "c", name: "Hello world"]
b 3 ["a", 3: "b", 10: "c", name: "Hello world"]
c 10 ["a", 3: "b", 10: "c", name: "Hello world"]
值得注意的是:
- 沒有輸出
name
。why? 因為name是arr的屬性,而0,3,10都是arr的索引。 - 這里的 index 是 Number 類型,並且也不會像 for-in 一樣遍歷原型鏈上的屬性。
forEach的性能
在不同瀏覽器下測試的結果都是 forEach 的速度不如 for。可以看到forEach主要應用在遍歷數組,但是它的性能並不如for,因此可以使用for就盡量不要使用forEach。
for-of(ES6中新增)
for-of
是ES6中新增的一個遍歷數組或者類數組的方法。它的出現主要是為了解決ES5中3種遍歷方式的缺陷:
- forEach 不能break 或者return
- for-in 的缺點更加明顯。它不僅遍歷了數組中的元素,還遍歷了自定義屬性,甚至連原型鏈上的屬性都被訪問到。
因此使用for-of
的優勢在於:
- 這是最簡潔、直接遍歷數組的方式
- 這個方法避開了for-in循環的缺陷
- 與forEach不同,它可以正確響應break,continue,return 語句。
說了這么多for-of
的優點,那么它有沒有缺點呢?
for-of
不支持普通對象遍歷,如果想要遍歷普通對象,使用for-in
例子:
let arr=[3,7,9];
for (let key of arr){
console.log(key);
}
結果
3
7
9
性能
由於
for-of
也是只遍歷可迭代對象的數據,相比於for-in
,效率會更高。
Other 循環方法
說明:下面的方法,Map,Reduce,Filter,Every,some方法都是面向數組的。不是普通對象。
map(不改變原數組)
map
方法(映射)給數組中的每個元素執行一次callback函數,執行callback函數的返回值組成一個新數組。未被初始化項、已經被刪除項(使用delete等方法)、數組的屬性
不會被執行callback函數。
請看如下例子:
let arr=new Array(5);
arr[1]=1;
arr[2]=2;
arr.name="hello,world!";
delete(arr[2]);
let fireArr=arr.map(item=>item*5);
console.log(fireArr);
輸出結果:
[empty,5,empty,empty,empty]
reduce
reduce
方法,讓數組的前項后項
進行某種計算。累計最終的結果。
例子:
//reduce方法
let arr = [4,7,8,3];
let total= arr.reduce( function (x, y) {
console.log(x);
return x + y;
});
console.log(total);
輸出結果
4
11
19
22
filter(不改變原數組)
filter
的意思是濾鏡、過濾器,顧名思義就是用來篩選符合某種條件的元素
,將符合條件的元素重新組成一個新的數組。
還是看一個例子:
let arr = [2,3,4,5,6];
arr.name=8;
let morearr = arr.filter(function (number) {
return number > 3
});
console.log(morearr);
輸出結果
4, 5, 6
在上面的例子中,將大於索引
大於3
的元素輸出。而不會輸出屬性。
every
every
方法為數組中的每個元素執行一次 callback 函數,當它找到一個使 callback 返回 false(表示可轉換為布爾值 false 的值)的元素,就立刻返回false。否則,callback為每個元素返回true,every就返回true。
看一面的例子
let arr = [1,2,3,4,5];
let result = arr.every(function (item, index) {
return item > 0
});
console.log(result);
返回結果
true
some
some
為數組中的每一個元素執行一次 callback 函數,直到找到一個使得 callback 返回一個“真值”(即可轉換為布爾值 true 的值)。如果找到了這樣一個值,some 將會立即返回 true。否則,some 返回 false。
let arr = [1,2,3,4,5];
let result = arr.some(function (item,index) {
return item > 3
});
console.log(result);
iterator
最后,還有一個東西不得不介紹,ES6新增的iterator
,迭代器。
看例子:
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
iterator
名叫“迭代器”,iter.next()
實際上是探測下一個位置是否有數據的探測器,如果有就返回false;如果下一個位置上沒有,就返回true。
效率問題
1、循環效率
for > for-of > forEach > filter > map > for-in
可以看到 的是for循環的速度是最快的,是最老的循環方法,也是優化得最好的;其次是for-of這個是es6才新增的循環非常好用,最慢是for-in,原因是它遍歷了原型鏈上的屬性。
2、some 和 every
他們都是根據判斷條件,返回給整個數組Boolean 值的方法,every 的執行速度會比some快很多。
- every是不滿足判斷條件后,立即返回“假的”false值給整個數組。否則繼續執行,返回true。
- some是滿足判斷條件后,立刻返回“真值”true給整個數組。否則繼續執行,返回false。