怎樣經過讀源碼提升你的 JavaScript 知識

做者:Carl Mungazijavascript

翻譯:瘋狂的技術宅html

原文:www.smashingmagazine.com/2019/07/jav…前端

未經容許嚴禁轉載java

你還記得本身第一次深刻挖掘經常使用的庫或框架的源代碼時的情景嗎?對我而言,那一刻是我三年前做爲前端開發人員的第一份工做。node

咱們剛剛完成了用於建立在線課程的內部遺留框架的重寫。在開始重寫時,咱們花時間研究了許多不一樣的解決方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。由於我是一個萌新(我剛重新聞轉向網絡開發),我記得每一個框架的複雜性都讓人感到懼怕,並且不理解框架的工做方式。react

當我開始更深刻地研究咱們選擇的 Mithril 框架時,個人能力增加了。從那之後,我對 JavaScript 的瞭解以及通常的編程方式獲得了很大的提升,我花了不少時間深刻研究天天在工做種或在本身的項目中使用的庫。在本文中,我將分享一些分析庫或框架的方法。git

Mithril 的超文本功能的源代碼

經過 Mithril 的 hyperscript 功能介紹如何去閱讀源代碼github

閱讀源代碼的好處

閱讀源代碼的好處之一是可使你學到更多的東西。當我第一次看到 Mithril 的代碼庫時,對虛擬 DOM 的含義只有一個模糊的概念。當我讀完時,就知道了虛擬 DOM 是一種技術,它涉及建立描述用戶界面的對象樹應該是什麼樣的。而後使用 DOM API(例如 document.createElement)將該樹轉換爲 DOM 元素。經過建立描述用戶界面將來狀態的新樹,而後將其與舊樹中的對象進行比較來執行更新。web

以前我已經在各類文章和教程中讀到過這些內容,雖然頗有幫助,可是在程序的上下文中可以觀察它對我來講是很是有啓發性的。它還告訴我在比較不一樣的框架時要問哪些問題。例如我如今不是去查看 GitHub 上的 star 數量,而是會問「每一個框架執行更新的方式如何影響性能和用戶體驗?」這樣的問題。chrome

另外一個好處是增長你對良好應用架構的理解。雖然大多數開源項目一般與其存儲庫遵循相同的結構,但每一個項目都包含差別。Mithril 的結構很是扁平,若是你熟悉它的 API,能夠對文件夾中的代碼進行有根據的猜想,好比renderrouterrequest 等。另外一方面,React 的結構也反映了它的新架構。維護者將負責 UI 更新的模塊(react-reconciler)與負責渲染 DOM 元素的模塊(react-dom)分開。

這樣作的好處之一是,開發人員如今能夠經過 hook 到 react-reconciler 包來編寫本身的自定義渲染器。我最近研究過的模塊捆綁包 Parcel 也有像 React 這樣的 packages 文件夾。密鑰模塊名爲 parcel-bundler,它包含負責建立捆綁包、熱啓動模塊服務器和命令行工具的代碼。

解釋 Object.prototype.toString 如何工做的 JavaScript 規範部分

不久以後,你正在閱讀的源代碼將引導你進入 JavaScript 規範

另外一個令我感到驚訝的好處是:你能夠更輕鬆地閱讀官方 JavaScript 規範,該規範定義了語言的工做方式。我第一次閱讀規範的時候是在分析 throw Errorthrow new Error 之間的區別。之因此要分析這個,是由於我注意到 Mithril 在其 m 函數的實現中使用了 throw Error,我想知道這樣是否是比 throw new Error 更好。從那之後,我也學會了邏輯運算符 &&|| 不必定返回布爾值,找到了控制 == 等式運算符如何強制賦值的[規則](http ://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison)和Object.prototype.toString.call({})返回 '[object Object]' 的[緣由](http://www.ecma- international.org/ecma-262/#sec-object.prototype.tostring) 。

閱讀源代碼的技巧

有不少方法能夠處理源代碼。我發現最簡單的方法是,從你選擇的庫中挑選一種方法,並去記錄調用它時會發生什麼。不是去記錄每一步,而是要嘗試肯定其總體流程和結構。

我最近用這種方法閱讀了 ReactDOM.render 的代碼 ,所以學到了不少關於 React Fibre 及其實現背後的一些原理。值得慶幸的是,因爲 React 是一個流行的框架,我在同一個問題上看到過不少其餘開發人員撰寫的文章,這也加快了這個過程。

這深刻探討並向我介紹了co-operative schedulingwindow.requestIdleCallback 方法和真實的鏈表的示例(React 經過把更新放入隊列來處理更新,這是優先更新的鏈表)。執行此類操做時,建議用庫建立一個很是基本的程序。這可使得調試時更容易,由於你不用去處理由其餘庫引發的棧跟蹤信息。

若是沒有對代碼進行深刻研究,我會正在處理的項目中打開 /node_modules 文件夾,或者轉到 GitHub 存儲庫。當我遇到錯誤或有趣的功能時,一般會發生這種狀況。在 GitHub 上閱讀代碼時,請確保你正在閱讀最新版本。你能夠經過單擊用於更改分支的按鈕,並選擇 「tags」 來查看帶有最新版本標記的代碼。庫和框架永遠在持續更新,因此你不但願把精力花費在下一版本中可能會刪除的內容。

還有另外一種閱讀源代碼的方式,我喜歡稱之爲「粗略一瞥」,這種方法並不那麼簡單。在我剛剛開始閱讀代碼的時候安裝了 express.js,我打開了它的 /node_modules 文件夾並瀏覽了它的依賴項。若是 README 沒有給我一個滿意的解釋,我就會閱讀源代碼。這樣作讓我獲得了一些有趣的發現:

  • Express 依賴兩個模塊,這兩個模塊均可以合併對象,可是合併方式的差別很大。 merge-descriptors 只添加在源對象上直接找到的屬性,它還合併了不可枚舉的屬性,而 utils-merge 只迭代對象的可枚舉屬性以及在其原型鏈中找到的屬性。 merge-descriptors 使用 Object.getOwnPropertyNames()Object.getOwnPropertyDescriptor(),而 utils-merge 使用了 for..in;
  • setprototypeof 模塊提供了一種跨平臺設置實例化對象原型的方式;
  • escape-html 是一個有 78 行代碼的模塊,用於轉義字符串內容,所以能夠用它在 HTML 內容中進行插值。

雖然閱讀源代碼的結果不太可能當即就能用得上,可是可以使你對本身使用的庫或框架的依賴關係有一個大體的瞭解,這是很是有用的。

在調試前端代碼時,瀏覽器的調試工具是你最好的朋友。除此以外,它們容許你隨時暫停程序並檢查其狀態、跳過函數的執行、進入或退出程序。不過有時這不可能當即作到,由於代碼有可能已經被壓縮過。我傾向於取消它們的通知,並將未經壓縮的代碼複製到 /node_modules 目錄中的相關文件裏。

ReactDOM.render function 的源代碼

像其餘程序同樣進行調試。造成一個假設,而後進行測試

案例研究:Redux的 Connect 函數

React-Redux 是一個用於管理 React 應用狀態的庫。在處理諸如此類的庫時,我首先會搜索已經編寫過有關其實現的文章。在這個案例研究中,我遇到了這篇文章(blog.isquaredsoftware.com/2018/11/rea…

connect 是一個 React-Redux 函數,它將 React 組件鏈接到應用程序的 Redux 存儲。怎麼樣?好吧,根據官方文檔的說明,它執行如下操做:

「...返回一個新的鏈接組件類,它將會包裝你傳入的組件。」

看完以後,我會問下列問題:

  • 我知不知道函數接受輸入的那些模式或概念,而後返回包含其餘功能的相同輸入?
  • 若是我知道此類模式,又將如何根據文檔中給出的解釋實現此模式?

一般,下一步是建立一個使用 connect 的很是基本的示例程序。可是在這種狀況下,我選擇使用咱們在 Limejump 上構建的新 React 程序,由於我想在程序的上下文中理解 connect,最終再進入生產環境。

我關注的組件看起來像這樣:

class MarketContainer extends Component {
 // code omitted for brevity
}

const mapDispatchToProps = dispatch => {
 return {
   updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today))
 }
}

export default connect(null, mapDispatchToProps)(MarketContainer);
複製代碼

它是一個容器組件,包裹着四個較小的鏈接組件。你在導出 connect 方法的文件中遇到的第一件事就是這個評論:connect 是 connectAdvanced 的外觀。這時咱們就有了第一個學習的點:有機會觀察外觀設計模式。在文件的末尾,咱們看到 connect 導出了一個名爲 createConnect 的函數的調用。它的參數是一堆默認值,它們已被解構,以下所示:

export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) 複製代碼

一樣,咱們遇到了另外一個學習的點:導出調用函數解構默認函數參數。解構部分是一個學習的點,由於代碼編寫以下:

export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory }) 複製代碼

它會致使這個錯誤 Uncaught TypeError:沒法解析 undefinednull 的屬性 connectHOC。這是由於該函數沒有默認參數能夠依賴。

注意有關此內容的更多信息,請閱讀 David Walsh 的文章(davidwalsh.name/destructuri…

createConnect 自己在其函數體中沒有任何功能。它返回一個名爲 connect 的函數,我在代碼裏使用的函數:

export default connect(null, mapDispatchToProps)(MarketContainer)
複製代碼

它須要四個參數,都是可選的,前三個參數根據參數是否存在及其值類型來定義它們的行爲,這是經過 match 函數來實現的。如今由於提供給 match 的第二個參數是導入 connect 的三個函數之一,我必須決定應該遵循哪一個線程。

在這裏學習的重點是:若是這些參數是函數,用於將第一個參數包裝爲 connect代理函數isPlainObject 用於檢查普通對象或 warning 模塊,它揭示瞭如何將調試器設置爲中斷全部異常。在匹配函數以後,咱們來到 connectHOC,這個函數接受咱們的 React 組件並將它鏈接到 Redux。它是另外一個函數調用,返回 wrapWithConnect,實際上它用來處理將組件鏈接到 store 的函數。

看一看 connectHOC 的實現,我能夠理解爲何它須要 connect 來隱藏它的實現細節。它是 React-Redux 的核心,其中包含不須要經過 connect 公開的邏輯。我將結束這裏的深度探討,若是我繼續的話,將是查閱我以前發現的參考資料的最佳時機,由於它包含了對代碼庫的很是詳細的解釋。

總結

剛開始閱讀源代碼時很困難,但與全部的事情同樣,隨着時間的推移會變得更容易。咱們的目標不是理解一切,而是要得到不一樣的思路和新知識。關鍵是要對整個過程進行深思熟慮,並對全部事物充滿好奇心。

例如,我發現 isPlainObject 函數頗有趣,由於它用 if (typeof obj !== 'object' || obj === null) return false 來確保給定的參數是普通對象。當我第一次閱讀它的代碼實現時,想知道爲何它沒有用Object.prototype.toString.call(opts)!=='[object Object]',這能夠用更少的代碼來區分對象和對象子類型,例如 Date 對象。可是閱讀下一行就會發現,當開發人員在使用 connect 返回 Date 對象的極不可能的事件中,將由 Object.getPrototypeOf(obj)=== null 檢查來進行處理。

isPlainObject 的另外一個吸引人的地方是這段代碼:

while (Object.getPrototypeOf(baseProto) !== null) {
    baseProto = Object.getPrototypeOf(baseProto)
}
複製代碼

谷歌搜索引導我找到這個 StackOverflow 帖子Redux issue,它們解釋了該代碼如何進行處理的案例,例如檢查源自 iFrame 的對象。

有用的連接

歡迎關注前端公衆號:前端先鋒,領取前端工程化實用工具包。

相關文章
相關標籤/搜索