lodash源碼分析之compact中的遍歷
小時候,
鄉愁是一枚小小的郵票,
我在這頭,
母親在那頭。
長大後,鄉愁是一張窄窄的船票,
我在這頭,
新娘在那頭。
後來啊,
鄉愁是一方矮矮的墳墓,
我在外頭,
母親在裏頭。
而現在,
鄉愁是一灣淺淺的海峽,
我在這頭,
大陸在那頭。
——余光中《鄉愁》
本文為讀 lodash 源碼的第三篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash
gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash
作用與用法
compact
函數用來去除數組中的假值,並返回由不為假值元素組成的新數組。
false
、null
、0
、 ""
、undefined
和 NaN
例如:
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
有兩個致命的特性:
for...in
的遍歷不能保證順序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,foo
(for...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
來遍歷同樣不會遍歷數組中稀疏數部分。
關於 Iterator
和 Generator
可以點擊參考中的鏈接詳細查看。
參考
- MDN:叠代器和生成器
- Iterator 和 for...of 循環
- Generator 函數的語法
- Lodash源碼講解(3)-compact函數
- MDN:for...of
- MDN:for…in
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步發送到微信公眾號上,歡迎關註,歡迎提意見:
作者:對角另一面
lodash源碼分析之compact中的遍歷