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

来源:http://www.cnblogs.com/hefty/archive/2017/11/20/7864869.html
-Advertisement-
Play Games

卑鄙是卑鄙者的通行證,高尚是高尚者的墓誌銘。 ——北島《回答》 看北島就是從這兩句詩開始的,高尚者已死,只剩卑鄙者在世間橫行。 本文為讀 lodash 源碼的第一篇,後續文章會更新到這個倉庫中,歡迎 star: "pocket lodash" gitbook也會同步倉庫的更新,gitbook地址: ...


卑鄙是卑鄙者的通行證,高尚是高尚者的墓誌銘。

——北島《回答》

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

本文為讀 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 會將數組當成稀疏數組對待。

密集數組VS稀疏數組

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

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

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

例如:

var sparse = new Array(10)
var dense = new Array(10).fill(undefined)

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

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

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

sparse.forEach(function(item){
  console.log(item)
})
dense.forEach(function(item){
  console.log(item)
})

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

源碼總覽

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

function slice(array, start, end) {
  let length = array == null ? 0 : array.length
  if (!length) {
    return []
  }
  start = start == null ? 0 : start
  end = end === undefined ? length : end

  if (start < 0) {
    start = -start > length ? 0 : (length + start)
  }
  end = end > length ? length : end
  if (end < 0) {
    end += length
  }
  length = start > end ? 0 : ((end - start) >>> 0)
  start >>>= 0

  let index = -1
  const result = new Array(length)
  while (++index < length) {
    result[index] = array[index + start]
  }
  return result
}

不傳參的情況

let length = array == null ? 0 : array.length
if (!length) {
  return []
}

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

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

處理start參數

start 參數用來指定截取的開始位置。

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

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

如果省略,則從索引0開始

start = start == null ? 0 : start

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

if (start < 0) {
  start = -start > length ? 0 : (length + start)
}

這段是處理負數的情況。

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

start >>>= 0

最後,用在 >>> 來確保 start 參數為整數或0。

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

處理end參數

end 參數用來指定截取的結束位置。

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

如果該參數為負數,則它表示在原數組中的倒數第幾個元素結束制取。

如果end被省略,則slice會一直提取到原數組的末尾。

如果end大於數組長度,slice也會一直提取到原數組末尾。

end = end === undefined ? length : end

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

end = end > length ? length : end

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

if (end < 0) {
  end += length
}

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

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

獲取新數組的長度

length = start > end ? 0 : ((end - start) >>> 0)

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

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

這裡同樣用了無符號右移位運算符來確保 length 為正數或0。

截取並返回新數組

let index = -1
const result = new Array(length)
while (++index < length) {
  result[index] = array[index + start]
}
return result

result 為新數組容器。

while 迴圈,從 start 位置開始,獲取原數組的值,依次存入新的數組中。

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

最後將 result 返回。

參考

  1. javascript權威指南(第6版), David Flanagan著,淘寶前端團隊譯,機械工業出版社
  2. why not the 'baseslice' func use Array.slice(), loop faster than slice?
  3. Array.prototype.slice()
  4. JavaScript: sparse arrays vs. dense arrays
  5. [譯]JavaScript中的稀疏數組與密集數組

License

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

作者:對角另一面


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 流程式控制制 與C語言不通的是python的流程式控制制代碼塊不是用{}花括弧表示的,而是強制縮進來控制的;而且縮進必須一致,官方推薦是使用4個空格,不建議使用tab(製表符)做縮進,一是不同的系統tab所占寬度不一致,會比較亂,二是由於python要求同級縮進必須保持一致,所以有些時候看上去是對齊l,但是 ...
  • Composer是一個非常流行的PHP包依賴管理工具,已經取代PEAR包管理器,對於PHP開發者來說掌握Composer是必須的. 對於使用者來說Composer非常的簡單,通過簡單的一條命令將需要的代碼包下載到vendor目錄下,然後開發者就可以引入包並使用了.其中的關鍵在於你項目定義的compo ...
  • 類的繼承 上面代碼中yello_person繼承了person父類。 子類中構造函數先對父類的構造函數進行繼承;然後加上自己的特有屬性。 18,19行示例如何使用對象分別調用父類和子類的方法 上面代碼中6行示例如何在構造函數中操作公有屬性,實現類似全局計數功能。 10行定義的方法示例如何在父類中列印 ...
  • 前言: 最近接手了一個項目,大概過了下需求,然後打開項目準備開搞的時候發現一個問題,這個項目是提供rest服務的一個web項目,其中很多關鍵的查詢都調用這個項目,之前的開發人員為了監控每個方法的執行時間,在方法開始和結束寫了很多logger.info("耗時:"+time)這種代碼。很顯然這是不合理 ...
  • 前言 本文可能不會詳細記錄每一步實現的過程,但一定程度上可以引領小伙伴走向更開闊的視野,串聯每個環節,呈現予你不一樣的效果。 業務規模 8個平臺 100+台伺服器 10+個集群分組 微服務600+ 用戶N+ 面臨問題 隨著分散式微服務容器技術的發展,傳統監控系統面臨許多問題: 容器如何監控 微服務如 ...
  • 產品或者服務由數據存儲和數據計算組成。pojo對象就是用於數據存儲。一旦確定後,整個應用或者產品的數據來源就確定。比如一個頁面或者功能需要使用什麼數據就可以快速找到對應的對象或者通過對象的關係找出來。 pojo對象屬於對系統的靜態描述。它應該是名詞,不應該是動詞或者其他。動詞、類型或者狀態等應該是算 ...
  • 前言 我們一般在做架構設計的時候,會經歷過三個階段:需求分析、概要設計和詳細設計。 1. 需求分析階段 : 主要梳理所有用例(Use case)和場景,並抽象出面向系統的用戶與角色,梳理出需求提供哪些功能與非功能的需求給這些用戶。 2. 概要設計階段 :根據需求分析的產物:核心需求,對整個系統進行模 ...
  • 閑話多說 免費報名:http://www.genshuixue.com/teacher/classCourseDetail/171117794648 .Net Core來了,帶給我們的是什麼?跨平臺,無疑是最大的亮點! Docker橫空出世,讓開發者和運維者都嘗到了甜頭! Jenkins持續集成,功 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...