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來執行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章