- 原文地址:Improve Your JavaScript Knowledge By Reading Source Code
- 原文做者:Carl Mungazi
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:MarchYuanx
- 校對者:imononoke, Baddyo
快速摘要:當你還處於編程生涯的初期階段時,深刻研究開源庫和框架的源代碼多是一項艱鉅的任務。在本文中,Carl Mungazi 分享了他如何克服恐懼,並開始用源碼來提升他的知識水平和專業技能。他還使用了 Redux 來演示他如何解構一個代碼庫。javascript
你還記得你第一次深刻研究你經常使用的庫或框架的源碼時的情景嗎?對我來講,這一刻發生在三年前我做爲前端開發者的第一份工做中。html
當時咱們剛剛完成了用於建立網絡學習課程的內部遺留框架的重構。在重構開始時,咱們花時間研究了許多不一樣的解決方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。那時我僅僅只是個小萌新(我剛重新聞工做轉向 web 開發),我記得我對每一個框架的複雜性感到恐懼,不理解它們是如何工做的。前端
隨着對咱們所選擇的 Mithril 框架研究的深刻,我對它的理解也逐漸加深了。從那之後,我花了不少時間深刻鑽研那些在工做或我的項目中平常使用的庫的內部結構,這顯著地提高了我對 JavaScript —— 以及通用編程思想 —— 的瞭解。在這篇文章中,我將分享一些方法給你,你可使用本身喜歡的庫或框架,並將其做爲學習工具。java
我要介紹的第一個源碼閱讀示例是 Mithril 的 hyperscript 函數。(高清預覽)node
閱讀源代碼的一個主要好處是能夠學到不少東西。在我第一次讀 Mithril 代碼庫時,我對虛擬 DOM 的概念還很模糊。當我讀完後,我瞭解到虛擬 DOM 是一種技術,它建立一個對象樹,用於描述用戶界面的外觀。而後使用 DOM APIs(如 document.createElement
)將對象樹轉換爲 DOM 元素。經過建立描述用戶界面的更新狀態的新對象樹,而後將其與舊對象樹進行比較來執行更新。react
我在各類文章和教程中已經閱讀了全部這些內容,雖然這頗有幫助,但對我來講,可以在咱們提供的應用程序的環境中觀察到它工做是很是有啓發性的。它還教會我在比較不一樣框架時應該考慮哪些因素。例如,我如今知道要考慮這樣的問題,「每一個框架執行更新的方式如何影響性能和用戶體驗?」,而不是隻看框架在 GitHub 上 star 的數量。android
另外一個好處是你對優秀的程序架構的理解和鑑賞能力提高了。雖然大多數開源項目的存儲庫一般遵循相同的結構,但每一個項目都包含差別。Mithril 的結構很是簡單,若是你熟悉它的 API,你能夠根據文件夾名稱推測出其中的代碼的功能,如 render
、router
和 request
。另外一方面,React 的結構反映了它的新架構。維護人員將負責 UI 更新的模塊(react concerner
)與負責呈現 DOM 元素的模塊(react dom
)分開。ios
這樣作的好處之一是,開發人員如今更容易經過掛進 react-reconciler
包來編寫本身的自定義渲染器。我最近研究過的模塊打包工具 Parcel 也有像 React 這樣的 packages
文件夾。主模塊名爲 parcel-bundler
,它包含負責建立包、啓動熱模塊服務器和命令行工具的代碼。git
不久以後,你所閱讀的源碼將引導你找到 JavaScript 規範。(高清預覽)github
另外一個好處 —— 令我感到驚訝的是 —— 你能夠更輕鬆地閱讀定義語言如何工做的官方 JavaScript 規範。我第一次閱讀規範是在研究 throw Error
與 throw new Error
(劇透警告 —— 兩者沒有區別)之間的區別時。我研究這個問題是由於我注意到 Mithril 在其 m
函數的實現中使用了 throw Error
,我想知道這種用法是否比使用 throw new Error 更好。從那之後,我還了解了邏輯運算符 &&
和 ||
不必定返回布爾值,找到了控制 ==
等於運算符如何強制轉換值的規則和 Object.prototype.toString.call({})
返回 '[object Object]'
的緣由。
有不少方法能夠處理源碼。我發現最簡單的方法是從你選擇的庫中選擇一個方法,並記錄當你調用它時會發生什麼。不要每個步驟都記錄,而是嘗試理解它的總體流程和結構。
我最近用這個方法閱讀了 ReactDOM.render 的源碼,所以學到了不少關於 React Fiber 及其實現背後的一些緣由。謝天謝地,因爲 React 是一個流行的框架,在一樣的問題上,我找到了不少其餘開發者撰寫的文章,這讓個人學習進程快了許多。
此次深刻研究還讓我明白了合做調度的概念、window.requestIdleCallback
方法和一個連接列表的實際示例(React 經過將更新放入一個隊列來處理它們,這個隊列是一個按優先級排列的連接列表)。在研究過程當中,建議使用庫建立很是基本的應用程序。這使得調試更容易,由於你沒必要處理由其餘庫引發的堆棧跟蹤。
若是我不打算進行深刻研究,我會打開正在開發的項目中的 /node_modules 文件夾,或者到 GitHub 倉庫中去查看源碼。這一般發生在我遇到一個 bug 或有趣的特性時。在 GitHub 上閱讀代碼時,請確保你閱讀的是最新版本。你能夠經過單擊用於更改分支的按鈕並選擇「tags」來查看具備最新版本標記的提交中的代碼。庫和框架永遠在進行更改,所以你不會想了解可能在下一版本中刪除的內容。
還有另外一種不太複雜的閱讀源碼的方法,我喜歡稱之爲「粗略一瞥」。在我開始閱讀代碼的早期,我安裝了 express.js,打開了它的 /node_modules
文件夾並瀏覽了它的依賴項。若是 README
沒有給我一個滿意的解釋,我就會閱讀源碼。這樣作讓我獲得了這些有趣的發現:
merge-descriptors
只添加直接在源對象上直接找到的屬性,它還合併了不可枚舉的屬性,而 utils-merge
只迭代對象的可枚舉屬性以及在其原型鏈中找到的屬性。merge-descriptors
使用 Object.getOwnPropertyNames()
和 Object.getOwnPropertyDescriptor()
而 utils-merge
使用 for..in
;setprototypeof
模塊提供了一種設置實例化對象原型的跨平臺方式;escape-html
是一個有 78 行代碼的模塊,用於轉義一系列內容,能夠在 HTML 內容中進行插值。雖然這些發現不可能當即有用,可是對庫或框架所使用的依賴關係有一個大體的瞭解是有用的。
在調試前端代碼時,瀏覽器的調試工具是你最好的朋友。除此以外,它們容許你隨時中止程序並檢查其狀態,跳過函數的執行或進入或退出程序。有時這不能當即生效,由於代碼已經壓縮。我傾向於將它解壓並將解壓的代碼複製到 /node_modules
文件夾中的對應文件中。
像處理任何其餘應用程序同樣處理調試。造成一個假設,而後測試它。(高清預覽)
React-Redux 是一個用於管理 React 應用程序狀態的庫。在處理這些流行的庫時,我首先搜索有關其實現的文章。在這個案例研究中,我找到了這篇文章。這是閱讀源碼的另外一個好處。研究階段一般會引導你閱讀這樣的信息性文章,這些文章會提升你的思考與理解。
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 is a facade over connectAdvanced。沒走多遠,咱們就有了第一個學習的時刻:一個觀察 facade 設計模式的機會。在文件末尾,咱們看到 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: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
。這是由於函數沒有可供回調的默認參數。
注意:有關這方面的更多信息,您能夠閱讀 David Walsh 的文章。根據你對語言的瞭解,一些學習時刻可能看起來微不足道,所以最好將注意力放在您之前從未見過的事情上,或須要瞭解更多信息的事情上。
createConnect
在其函數內部並不執行任何操做。它只是返回一個名爲 connect 的函數,也就是我在這裏用到的:
export default connect(null, mapDispatchToProps)(MarketContainer)
複製代碼
它須要四個參數,都是可選的,前三個參數都經過 match 函數來幫助根據參數是否存在以及它們的值類型來定義它們的行爲。如今,由於提供給 match
的第二個參數是導入 connect
的三個函數之一,我必須決定要遵循哪一個線程。
若是那些參數是函數,代理函數被用來將第一個參數包裝爲 connect
,這是也一個學習的時刻。isPlainObject 用於檢查普通對象或 warning 模塊,它揭示瞭如何將調試器設置爲中斷全部異常。在匹配函數以後,咱們來看 connectHOC
,這個函數接受咱們的 React 組件並將它鏈接到 Redux。它是另外一個函數調用,返回 wrapWithConnect,該函數實際處理將組件鏈接到存儲的操做。
看看 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 問答和這個在 GitHub 倉庫中的 Redux issue,解釋該代碼如何處理諸如檢查源自 iFrame 的對象這類狀況。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。