通過閱讀原始碼來提高js知識
原文傳送門:《Improve Your JavaScript Knowledge By Reading Source Code》
原本作者:Carl Mungazi,是位於倫敦的能源創業公司Limejump的前端開發人員。他花時間深入挖掘所有JavaScript的深度。
簡介:當你還處於程式設計生涯的初期階段時,深入研究開源庫和框架的原始碼可能是一項艱鉅的任務。在這篇文章中,Carl Mungazi分享了他如何克服恐懼並開始使用原始碼來提高他的知識和技能。他還使用Redux來演示他如何破壞庫。
你還記得你第一次深入挖掘經常使用的庫或框架的原始碼嗎?對我而言,那一刻是我三年前作為前端開發人員的第一份工作。
我們剛剛完成了用於建立電子學習課程的內部遺留框架的重寫。在重寫開始時,我們花時間研究了許多不同的解決方案,包括Mithril,Inferno,Angular,React,Aurelia,Vue和Polymer。因為我是一個非常初學者(我剛從新聞轉向網路開發),我記得每個框架的複雜性讓人感到害怕,而不是理解每個框架的工作方式。
當我開始更深入地研究我們選擇的框架Mithril時,我的理解增長了。從那以後,我對JavaScript的瞭解 - 以及一般的程式設計 - 得到了很大的幫助,我花了很多時間深入研究我每天在工作或我自己的專案中使用的庫的內容。在這篇文章中,我將分享一些您可以採用自己喜歡的圖書館或框架並將其用作教育工具的方法。
以下是我第一次閱讀程式碼的介紹是通過Mithril的hyperscript函式
閱讀原始碼的好處
閱讀原始碼的主要好處之一是增加了你可以學習的知識數量。當我第一次看到Mithril的程式碼庫時,我對虛擬DOM的含義有一個模糊的概念。當我學習完後,我知道虛擬DOM是一種技術,它涉及建立描述使用者介面應該是什麼樣的物件樹。然後使用DOM API將該樹轉換為DOM元素document.createElement
。通過建立描述使用者介面的未來狀態的新樹,然後將其與舊樹中的物件進行比較來執行更新。
我在各種文章和教程中也都閱讀過這些內容,它對我很有幫助,並且在我們釋出的應用程式的中能夠觀察它的運作,對我來說非常有啟發性。它還告訴我在比較不同的框架時要問哪些問題。例如,我現在會問諸如“每個框架執行更新的方式如何影響效能和使用者體驗?”等問題,而不是關注GitHub上的Stars。
另一個好處是增加你對優秀應用程式架構的好感和理解。雖然大多數開源專案通常與其儲存庫遵循相同的結構,但每個專案都各有差異。Mithril的結構是非常平坦的,如果你熟悉它的API,你可以猜測有關資料夾,如程式碼render
,router
和request
。另一方面,React的結構反映了它的新架構。維護者將負責UI更新(react-reconciler
)的模組與負責呈現DOM元素(react-dom
)的模組分開。
這樣做的好處之一是,現在開發人員可以通過掛鉤來編寫自己的自定義渲染器react-reconciler
。我最近研究過的模組捆綁包Parcel也有像React packages
這樣的資料夾。關鍵的模組已命名parcel-bundler
,它包含負責建立捆綁包,啟動熱模組伺服器和命令列工具的程式碼。
不久之後,你正在閱讀的原始碼將引導您進入JavaScript規範。
另一個好處 —— 令我感到驚訝的是:你可以更輕鬆地閱讀那些定義JavaScript這門語言如何工作的官方規範。我第一次讀規範是在我調查的區別throw Error
和throw new Error
(擾流警報是none)。我調查了這個因為我注意到Mithril用於throw Error
實現它的m
功能,我想知道使用它是否比throw new Error
更好。從那以後,我還了解到,邏輯運算子&&
和||
不一定返回布林值,發現這個 ==
運算是如何強制轉換值的規則 ,以及還有Object.prototype.toString.call({}) 返回 '[object Object]'
的理由 。
閱讀原始碼的技巧
有很多方法可以處理原始碼。我發現最簡單的方法是從您選擇的庫中選擇一種方法並記錄呼叫它時會發生什麼。不要記錄每一步,而要嘗試確定其整體流程和結構。
我最近按這個方式做了ReactDOM.render
的記錄,也因此學到了很多關於React Fiber及其實現背後的一些原因。值得慶幸的是,由於React是一個流行的框架,我在同一個問題上遇到了很多其他開發人員撰寫的文章,這讓我少走很多彎路。
這次深入探討還向我介紹了合作排程的概念,window.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
資料夾中的相關檔案中,再將其進行解析。
案例研究:Redux的Connect連線功能
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是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: 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社群 或 Redux issue 檢視關於該程式碼如何處理的案例,例如檢查iFrame的物件來源。
另外,還有一些有利於閱讀原始碼的文章
- 《如何通過逆向工程進行框架設計》,Max Koretskyi,Medium
- 《如何閱讀程式碼》 ,Aria Stewart,GitHub
【作者簡介】:土撥鼠,蘆葦科技web前端開發工程師,代表作品:飛花亭小程式、續航基因、YY表情紅包、YY疊方塊直播競賽小遊戲。擅長網站建設、公眾號開發、微信小程式開發、小遊戲、公眾號開發,專注於前端框架、服務端渲染、SEO技術、互動設計、影像繪製、資料分析等研究。
歡迎和我們一起並肩作戰: web@talkmoney.cn
訪問 www.talkmoney.cn 瞭解更多