天天看點

讀lodash源碼之從slice看稀疏數組與密集數組

卑鄙是卑鄙者的通行證,高尚是高尚者的墓志銘。 ——北島《回答》

看北島就是從這兩句詩開始的,高尚者已死,隻剩卑鄙者在世間橫行。

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

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

你可能會有點奇怪,原生的 slice 方法基本沒有相容性的問題,為什麼 lodash 還要實作一個 slice 方法呢?

這個問題,lodash 的作者已經在 why not the 'baseslice' func use Array.slice(), loop faster than slice? 的 issue 中給出了答案:lodash 的 slice 會将數組當成密集數組對待,原生的 slice 會将數組當成稀疏數組對待。

我們先來看看犀牛書是怎樣定義稀疏數組的:

稀疏數組就是包含從0開始的不連續索引的數組。通常,數組的length屬性值代表數組中元素的個數。如果數組是稀疏的,length屬性值大于元素的個數。

如果數組是稀疏的,那麼這個數組中至少有一個以上的位置不存在元素(包括 <code>undefined</code> )。

例如:

其中 <code>sparse</code> 的 <code>length</code> 為10,但是 <code>sparse</code> 數組中沒有元素,是稀疏數組;而 <code>dense</code> 每個位置都是有元素的,雖然每個元素都為<code>undefined</code>,為密集數組 。

那稀疏數組和密集數組有什麼差別呢?在 lodash 中最主要考慮的是兩者在疊代器中的表現。

稀疏數組在疊代的時候會跳過不存在的元素。

<code>sparse</code> 根本不會調用 <code>console.log</code> 列印任何東西,但是 <code>dense</code> 會列印出10個 <code>undefined</code> 。

當然,除了對待稀疏數組跟原生的 slice 不一緻外,其他的規則還是一樣的,下面是 lodash 實作 slice 的源碼。

不傳參時,<code>length</code> 預設為0,否則擷取數組的長度。注意這裡用的是 <code>array == null</code> ,非 <code>array === null</code> ,包含了 <code>undefined</code> 的判斷。

是以在不傳參調用 lodash 的 slice 時,傳回的是空數組,而原生的 slice 沒有這種調用方式。

<code>start</code> 參數用來指定截取的開始位置。

先來看下 MDN 對該參數的描述:

如果該參數為負數,則表示從原數組中的倒數第幾個元素開始提取。 如果省略,則從索引0開始

是以這段是處理省略的情況,省略時,預設值為0。

這段是處理負數的情況。

如果負數取反後比數組的長度還要大,即超出了數組的範圍,則取值為0,表示從開始的位置截取,否則用 <code>length + start</code> ,即向後倒數。

最後,用在 <code>&gt;&gt;&gt;</code> 來確定 <code>start</code> 參數為整數或0。

因為 lodash 的 slice 除了可以處理數組外,也可以處理類數組,是以第一個參數 <code>array</code> 可能為一個對象, <code>length</code> 屬性不一定為數字。

<code>end</code> 參數用來指定截取的結束位置。

同樣來看下 MDN 對些的描述:

如果該參數為負數,則它表示在原數組中的倒數第幾個元素結束制取。 如果end被省略,則slice會一直提取到原數組的末尾。 如果end大于數組長度,slice也會一直提取到原數組末尾。

這段是處理 <code>end</code> 被省略的情況,省略時,<code>end</code> 預設為為 <code>length</code>,即截取到數組的末尾。

這是處理 <code>end</code> 比數組長度大的情況,如果被數組長度大,也會截取到數組的末尾。

這段是處理負值的情況,如果為負值,則從數組末尾開始向前倒數。

這裡沒有像 <code>start</code> 一樣控制 <code>end</code> 的向前倒數完後是否為負數,因為後面還有一層控制。

新數組的長度計算方式很簡單,就是用 <code>edn - start</code> 即可得出。

上面說到,沒有控制最終 <code>end</code> 是否為負數的情況。這裡用的是 <code>start</code> 和 <code>end</code> 的比較,如果 <code>start</code> 比 <code>end</code> 大,則新數組長度為0,即傳回一個空數組。否則用 <code>end - start</code> 來計算。

這裡同樣用了無符号右移位運算符來確定 <code>length</code> 為正數或0。

<code>result</code> 為新數組容器。

用 <code>while</code> 循環,從 <code>start</code> 位置開始,擷取原數組的值,依次存入新的數組中。

因為是通過索引取值,如果遇到稀疏數組,對應的索引值上沒有元素時,通過數組索引取值傳回的是 <code>undefined</code>, 但這并不是說稀疏數組中該位置的值為 <code>undefined</code> 。

最後将 <code>result</code> 傳回。

javascript權威指南(第6版), David Flanagan著,淘寶前端團隊譯,機械工業出版社

why not the 'baseslice' func use Array.slice(), loop faster than slice?

Array.prototype.slice()

JavaScript: sparse arrays vs. dense arrays

[譯]JavaScript中的稀疏數組與密集數組

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

作者:對角另一面