1. 程式人生 > >lodash源碼分析之compact中的遍歷

lodash源碼分析之compact中的遍歷

gitbook def http 出現 article 但是 關於 post 順序

小時候,

鄉愁是一枚小小的郵票,

我在這頭,

母親在那頭。

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

我在這頭,

新娘在那頭。

後來啊,

鄉愁是一方矮矮的墳墓,

我在外頭,

母親在裏頭。

而現在,

鄉愁是一灣淺淺的海峽,

我在這頭,

大陸在那頭。

——余光中《鄉愁》

本文為讀 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 } }

這樣寫,肯定是沒有問題的,但是數組不總是密集的,也有可能是稀疏數組,假如:var arr = [1,2,3,,4,,5] 這樣的稀疏數組,會出現2次無效的循環。

關於稀疏數組,可以看本系列的第一篇文章《讀lodash源碼之從slice看稀疏數組與密集數組》。

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 來遍歷數組是安全的,因為這個方法是數組的原生方法,而且使用 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)

最後,所有文章都會同步發送到微信公眾號上,歡迎關註,歡迎提意見: 技術分享圖片

作者:對角另一面

lodash源碼分析之compact中的遍歷