1. 程式人生 > >js陣列迴圈的研究

js陣列迴圈的研究

為什麼會寫這個?

同事的疑問

let a = []
a[5] = 1
console.log(a.length)
a.forEach(function(item) {
  console.log(item);
});

結果是這樣的

按理來說,不是應該迴圈6次的麼,是不是迴圈的方法不對,我們試試用其他的

一樣,我們看一下a是怎樣的

可以看到,長度為6,但是前面5個全是空,也就是說,會跳過空的內容,Google了一番,找到這篇文

JavaScript陣列map方法的疑問

裡面提到一個點就是,什麼都沒有的陣列元素叫做槽(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來執行