爲什麼會寫這個?
同事的疑問
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來執行