lodash源碼分析之compact中的遍歷

小時候,

鄉愁是一枚小小的郵票,javascript

我在這頭,java

母親在那頭。git

長大後,鄉愁是一張窄窄的船票,es6

我在這頭,github

新娘在那頭。數組

後來啊,安全

鄉愁是一方矮矮的墳墓,微信

我在外頭,app

母親在裏頭。ide

而如今,

鄉愁是一灣淺淺的海峽,

我在這頭,

大陸在那頭。

——余光中《鄉愁》

本文爲讀 lodash 源碼的第三篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash

gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash

做用與用法

compact 函數用來去除數組中的假值,並返回由不爲假值元素組成的新數組。

falsenull0""undefinedNaN 都爲假值。

例如:

var arr = [1,false,2,null,3,0,4,NaN,5,undefined]
_.compact(arr) // 返回 [1,2,3,4,5]

源碼

function compact(array) {
  let resIndex = 0
  const result = []

  if (array == null) {
    return result
  }

  for (const value of array) {
    if (value) {
      result[resIndex++] = value
    }
  }
  return result
}

compact 的源碼只有寥寥幾行,至關簡單。

首先判斷傳入的數組是否爲 null 或者 undefined,若是是,則返回空數組。

而後用 for...of 來取得數組中每項的值,若是不爲假值,則存入新數組 result 中,最後將新數組返回。

到這裏,源碼分析完了。

可是在看源碼的時候,發現這裏用了 for...of 來作遍歷,其實除了 for...of 外,也能夠用 for 或者 for...in 來作遍歷,那爲何最後選了 for...of 呢?

數組中的for循環

使用 for 循環,很容易就將 compact 中關於循環部分的源碼改寫成如下形式:

for (let i = 0; i < array.length; i++) {
      const value = array[i]
    if (value) {
      result[resIndex++] = value
    }
  }

這樣寫,確定是沒有問題的,可是不夠簡潔。

for…in

再來看 for...in 循環,先來將源碼改寫一下:

for (let index in array) {
  const value = array[i]
  if (value) {
    result[resIndex++] = value
  }
}

先看看MDN上關於 for...in 的用法:

for...in語句以任意順序遍歷一個對象的 可枚舉屬性

關於可枚舉屬性,能夠點擊上面的連接到MDN上了解一下,這裏不作太多的解釋。

在數組中,數組的索引是可枚舉屬性,能夠用 for...in 來遍歷數組的索引,數組中的稀疏部分不存在索引,能夠避免用 for 循環形成無效遍歷的弊端。

可是,for...in 有兩個致命的特性:

  1. for...in 的遍歷不能保證順序
  2. for...in 會遍歷全部可枚舉屬性,包括繼承的屬性。

for...in 的遍歷順序依賴於執行環境,不一樣執行環境的實現方式可能會不同。單憑這一點,就斷然不能在數組遍歷中使用 for...in,大多數狀況下,順序對於數組的遍歷都至關重要。

關於第二點,先看個例子:

var arr = [1,2,3]
arr.foo = 'foo'
for (let index in arr) {
  console.log(index)
}

在這個例子中,你指望輸出的是 0,1,2,可是最後輸出的多是 0,1,2,foofor...in 不能保證順序)。由於 foo 也是可枚舉屬性,在 for..in 會被遍歷出來。

for…of

最後來看看 for...of

當咱們在控制檯中打印一個數組,並將它展開來查看時,會在數組的原型鏈上發現一個很特別的屬性 Symbol.iterator

其實 for...of 循環內部調用的就是數組原型鏈上的 Symbol.iterator 方法。

Symbol.iterator 在調用的時候會返回一個遍歷器對象,這個遍歷器對象中包含 next 方法,for...of 在每次循環的時候都會調用 next 方法來獲取值,直到 next 返回的對象中的 done屬性值爲 true 時中止。

其實咱們也能夠手動調用來模擬遍歷的過程:

const arr = [1,2,3]
const iterator = a[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}

知道這些原理後,徹底能夠改寫數組中的 Symbol.iterator 方法,例如遍歷時將數組中的值都乘2:

Array.prototype[Symbol.iterator] = function () {
  let index = 0
  const _self = this
  return {
    next: function () {
      if (index < _self.length) {
        return {value: _self[index++] * 2, done: false}
      } else {
        return {done: true}
      }
    }
  }
}

使用 Generator 函數能夠寫成如下的形式:

Array.prototype[Symbol.iterator] = function* () {
  let index = 0
  while (index < this.length) {
    yield this[index++] * 2   
  }
}

所以在不改寫 Symbol.iterator 的狀況下,使用 for...of 來遍歷數組是安全的,由於這個方法是數組的原生方法。

關於 IteratorGenerator 能夠點擊參考中的連接詳細查看。

參考

  1. MDN:迭代器和生成器
  2. Iterator 和 for...of 循環
  3. Generator 函數的語法
  4. Lodash源碼講解(3)-compact函數
  5. MDN:for...of
  6. MDN:for…in

License

署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)

最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:

做者:對角另外一面

相關文章
相關標籤/搜索