js陣列迴圈的研究
為什麼會寫這個?
同事的疑問
let a = []
a[5] = 1
console.log(a.length)
a.forEach(function(item) {
console.log(item);
});
結果是這樣的
按理來說,不是應該迴圈6次的麼,是不是迴圈的方法不對,我們試試用其他的
一樣,我們看一下a是怎樣的
可以看到,長度為6,但是前面5個全是空,也就是說,會跳過空的內容,Google了一番,找到這篇文
裡面提到一個點就是,什麼都沒有的陣列元素叫做槽(slot),一般方法都會忽略,還說到了關於V8原始碼裡面對於陣列的定義
function ArrayMap(f, receiver) { CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map"); // Pull out the length so that modifications to the length in the // loop will not affect the looping and side effects are visible. var array = TO_OBJECT(this); var length = TO_LENGTH(array.length); if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f); var result = ArraySpeciesCreate(array, length); for (var i = 0; i < length; i++) { if (i in array) { var element = array[i]; %CreateDataProperty(result, i, %_Call(f, receiver, element, i, array)); } } return result; }
可以看到裡面有一句if (i in array),也就是我們建立陣列的時候,只是一個指標,如果沒有內容,是不會實際建立的
如果我們是在需要建立一個空的陣列,又需要迴圈,我們可以採取這種方法
Array.from(new Array(4))
可以看到,迴圈生效了,雖然結果是undefined
陣列的迴圈
由此,就引發了我一個想法,js裡面的陣列的迴圈,或者說遍歷分別有哪些呢
1. for-in
var a = [1, 2, 3];
for (var i in a) {
console.log(a[i]);
}
// 1
// 2
// 3
但是其實for-in不是很好,因為for-in 迴圈遍歷的是物件的屬性,而不是陣列的索引。因此, for-in 遍歷的物件便不侷限於陣列,還可以遍歷物件
<script>
var a = [1, 2, 3];
a.foo = true
console.log(a)
for (var i in a) {
console.log(a[i]);
}
</script>
所以不建議用,並且我們遍歷的是物件,它的順序也是不固定的,很奇怪
<script> let person = { fname: "san", lname: "zhang", age: 99, 1: 2 }; let info; for (info in person) { console.log("person[" + info + "] = " + person[info]); } </script>
可以看到,key為1的先出來的,關於這個其實也有討論,在這裡就不展開了,有興趣可以去看這篇文,一句話概括就是
先遍歷出整數屬性(integer properties,按照升序),然後其他屬性按照建立時候的順序遍歷出來。
2.陣列自帶的遍歷?
其實陣列自身就有很多遍歷可供我們使用
- Array.prototype.forEach陣列物件內建方法
- Array.prototype.map陣列物件內建方法
- Array.prototype.filter陣列物件內建方法
- Array.prototype.reduce陣列物件內建方法
- Array.prototype.some陣列物件內建方法
- Array.prototype.every陣列物件內建方法
- Array.prototype.indexOf陣列物件內建方法
- Array.prototype.lastIndexOf陣列物件內建方法
(1) forEach
php裡面最常用
var a = [1, 2, 3];
a.forEach(function (value, key, arr) {
console.log(value) // 結果依次為1,2,3
console.log(key) // 結尾依次為0,1,2
console.log(arr) // 三次結果都為[1,2,3]
})
這個方法就只是單純的迴圈,通過回撥函式來提取你需要的資料,處理你的業務邏輯
map
map這個方法可以通過遞迴,然後在回撥裡面return一個新的內容,用這一些資訊的內容組成一個新的陣列來返回給你,而且並不會改變原來的陣列結構
<script>
var a = [1, 2, 3];
var b = a.map(function (value, key, arr) {
console.log(value) // 結果依次為1,2,3
console.log(key) // 結尾依次為0,1,2
console.log(arr) // 三次結果都為[1,2,3]
return value + 1;
})
console.log(a); // 結果為[ 1, 2, 3 ]
console.log(b); // 結果為[ 2, 3, 4 ]
</script>
很好用,特別在對於結合我們vue的時候特別有用,很多時候我們在拿到後端資料的時候然後做資料繫結的時候,需要對陣列做加工,例如,我們這個陣列給表格用的,我們的表格有可能會有一些額外需要顯示的屬性
<script>
var a = [{
x: 100,
y: 200
}, {
x: 500,
y: 200
}];
var b = a.map(function (value, key, arr) {
return {
x: value.x,
y: value.y,
z: value.x + value.y
}
})
console.log(a)
console.log(b)
</script>
(2) filter(過濾器)
顧名思義,這是一個給我們過濾的方法,直接上程式碼
var a = [1,2,3];
var b = a.filter(function(value,key,arr){
console.log(value) // 結果依次為1,2,3
console.log(key) // 結尾依次為0,1,2
console.log(arr) // 三次結果都為[1,2,3]
if(value === 3){
return false;
}
return true;
})
console.log(a); // 結果為[ 1, 2, 3 ]
console.log(b); // 結果為[ 1,2 ]
在回撥裡面如果我們返回了true,這個就意味著,我們要,如果是false就是說這個我們不要,最後組成一個新的陣列,返回給你,並不會影響原陣列
(3) reduce(減少)
英文名字是減少,但是更多我們使用的時候其實是累加,一般用於數組裡面數據的累加或者組合
<script>
var num = [1, 2, 3, 4, 5];
var res = num.reduce(function (total, num, index, arr) {
console.log('現在是第' + index + '個' + 'total的值為:' + total + '====num的值為' + num)
return total + num;
})
console.log(res)
</script>
在這裡我們需要注意一個點,我們遞迴竟然是從index為1開始,而且這個時候,第二個引數num,是數組裡面第一個的值,其實很好理解,因為我們裡面拿到的第一個引數是陣列的上一個迴圈裡面返回的,陣列第一個壓根就沒有上一個,所以就直接跳過了第一個,但是有的時候我們需要也操作第一個怎麼辦,可以這樣
<script>
var num = [1, 2, 3, 4, 5];
var res = num.reduce(function (total, num, index, arr) {
console.log('現在是第' + index + '個' + 'total的值為:' + total + '====num的值為' + num)
return total + num;
}, 10)
console.log(res)
</script>
這個方法平時我們用得比較少,其實很有用的,例如合併二維陣列
<script>
var red = [
[0, 1],
[2, 3],
[4, 5]
].reduce(function (a, b) {
return a.concat(b);
}, []);
console.log(red)
</script>
(4) find和some和every
find這個方法顧名思義就是找到數組裡面合適我們條件的那一個
<script>
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
var ret1 = arr1.find((value, index, arr) => {
return value > 4
})
var ret2 = arr1.find((value, index, arr) => {
return value > 14
})
console.log('%s', ret1)
console.log('%s', ret2)
</script>
找不到就undefined
some和find十分相似,也是找到有符合條件,不過,返回的是一個布林值
<script>
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
var ret1 = arr1.some((value, index, arr) => {
return value > 4
})
var ret2 = arr1.some((value, index, arr) => {
return value > 14
})
console.log('%s', ret1)
console.log('%s', ret2)
</script>
而every就是找到有一個不符合條件的就返回true
這裡你可以有疑問,其實上面的map等方法我們都可以找到.為什麼我們要用這個,因為幾個方法有一個好的地方,就是找到了以後,不會繼續執行下去,而map等方法是會繼續的
3.for-of
上面的方法都很不錯,都很好,但是他們都存在同一個問題
不能正確響應 break, continue, return
就是說,我們不能人為的中途干預說我不想繼續迴圈了,所以es6就引入亂for-of
回到我們一開始使用for-in的例子,替換成for-of,可以看到,成功執行了
<script>
var a = [1, 2, 3];
a.foo = true
console.log(a)
for (info of a) {
console.log(info)
}
</script>
但是這個方法也有一個問題,拿不到index,這個時候怎麼辦,其實這個方法不單單可以迴圈陣列,只要具備Iterator介面的都可以,我們可以這樣
<script>
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
const arrIte = arr.entries()
for (const [key, val] of arrIte) {
console.log('key=>' + key + ' val=>' + val)
}
</script>
總結
那麼,說了那麼多,到底我們應該用哪個迴圈比較好?其實這是一個比較開放性的問題,因為單純輪效能來說,for的效率的最高的
但是很多時候,我們在業務的操作用,單單用for的話,我可能得額外又定義一些變數來幫助我計算出最好的結果,而且for的可讀性並沒有那一堆map/some等等的高,所以個人建議,在資料量不大的情況下儘量使用map/forEach等方法,需要迴圈的資料特別大的情況下可以酌情使用for來執行