目前網(wǎng)上有很多「XX源碼分析」這樣的文章,不過(guò)這些文章分析源碼的范圍有限,有時(shí)候講的內(nèi)容不是讀者最關(guān)心的。愛(ài)掏網(wǎng) - it200.com同時(shí)我也注意到,源碼是在不斷更新的,文章里寫(xiě)的源碼往往已經(jīng)過(guò)時(shí)了。愛(ài)掏網(wǎng) - it200.com因?yàn)檫@些問(wèn)題,很多同學(xué)都喜歡自己看源碼,自己動(dòng)手,豐衣足食。愛(ài)掏網(wǎng) - it200.com
這篇文章主要講的是閱讀大型的前端開(kāi)源項(xiàng)目比如 React、Vue、Webpack、Babel 的源碼時(shí)的一些技巧。愛(ài)掏網(wǎng) - it200.com目的是讓大家在遇到需要閱讀源碼才能解決的問(wèn)題時(shí),可以更快的定位到自己想看的代碼。愛(ài)掏網(wǎng) - it200.com授人以魚(yú)不如授人以漁,希望大家可以通過(guò)這篇博客,了解到閱讀大型前端項(xiàng)目源碼時(shí)的切入點(diǎn)。愛(ài)掏網(wǎng) - it200.com在之后遇到好奇的問(wèn)題時(shí),可以自己去探索。愛(ài)掏網(wǎng) - it200.com
問(wèn)題驅(qū)動(dòng)——不要為了看源碼而看源碼
首先我們要明確一點(diǎn),看源碼的目的是什么?
我個(gè)人的意見(jiàn)是,看源碼是為了解決問(wèn)題。愛(ài)掏網(wǎng) - it200.com開(kāi)源項(xiàng)目的源代碼并沒(méi)有什么非常特殊的地方,也都是普通的代碼。愛(ài)掏網(wǎng) - it200.com這些代碼的數(shù)量級(jí)一般都挺大,如果想是從源碼中學(xué)到東西,直接瀏覽整個(gè) Codebase 無(wú)疑是大海撈針。愛(ài)掏網(wǎng) - it200.com
但如果是帶著問(wèn)題去看源碼,比如想了解一下 React 的合成事件系統(tǒng)的原理,想了解 React 的 setState 前后發(fā)生了什么,或者想了解 Webpack 插件系統(tǒng)的原理。愛(ài)掏網(wǎng) - it200.com也有可能是遇到了一個(gè) bug,懷疑是框架/工具的問(wèn)題。愛(ài)掏網(wǎng) - it200.com在這樣的情況下,帶著一個(gè)具體的目標(biāo)去看源碼,就會(huì)有的放矢。愛(ài)掏網(wǎng) - it200.com
看最新版的源碼
之前看到一種說(shuō)法,看源碼要從項(xiàng)目的第一個(gè) commit 開(kāi)始看。愛(ài)掏網(wǎng) - it200.com如果是為了解決前文中對(duì)框架/工具產(chǎn)生的困惑,那自然要看當(dāng)前項(xiàng)目中用到的框架/工具的版本。愛(ài)掏網(wǎng) - it200.com
如果是為了學(xué)習(xí)源碼,我也建議看最新的源碼。愛(ài)掏網(wǎng) - it200.com因?yàn)橐粋€(gè)項(xiàng)目是在不斷迭代和重構(gòu)的。愛(ài)掏網(wǎng) - it200.com不同版本之間可能是一次完全的重寫(xiě)。愛(ài)掏網(wǎng) - it200.com比如 Vue 2.x 和 React 16。愛(ài)掏網(wǎng) - it200.com重構(gòu)導(dǎo)致了代碼架構(gòu)上的一些變化,Vue 2.x 引入了 Vritual DOM,Pull + Push 的數(shù)據(jù)變化檢測(cè)方式讓整個(gè)代碼的結(jié)構(gòu)變的更清晰了,所以 2.x 的代碼其實(shí)比 1.x 的更容易閱讀。愛(ài)掏網(wǎng) - it200.comReact 16 重寫(xiě)了 Reconciler,引入了 fiber 這個(gè)概念,整個(gè)代碼倉(cāng)庫(kù)結(jié)構(gòu)也更清晰,所以更推薦閱讀。愛(ài)掏網(wǎng) - it200.com
前置條件
看源碼怎么看,當(dāng)然不能一把梭了。愛(ài)掏網(wǎng) - it200.com
看源碼之前需要對(duì)項(xiàng)目的原理有一個(gè)基本的了解。愛(ài)掏網(wǎng) - it200.com所謂原理就是,這個(gè)項(xiàng)目有哪些組成部分,為了達(dá)到最終的產(chǎn)出,要經(jīng)過(guò)哪幾步流程。愛(ài)掏網(wǎng) - it200.com這些流程里,業(yè)界主流的方案有哪幾種。愛(ài)掏網(wǎng) - it200.com
比如前端 View 層框架,要渲染出 UI,組件要經(jīng)過(guò) mount、 render 等等步驟。愛(ài)掏網(wǎng) - it200.com數(shù)據(jù)驅(qū)動(dòng)的前端框架,在 mounted 之后,就會(huì)進(jìn)入一個(gè)循環(huán),當(dāng)用戶(hù)交互觸發(fā)組件數(shù)據(jù)變化時(shí),會(huì)更新 UI。愛(ài)掏網(wǎng) - it200.com其中數(shù)據(jù)的檢測(cè)方式又有分 Push 和 Pull 兩種方案。愛(ài)掏網(wǎng) - it200.com渲染 UI 可以是全量的字符串模板替換,也可以是基于 Virtual DOM 的差量 DOM 更新。愛(ài)掏網(wǎng) - it200.com
又比如前端的一些工具,Webpack 和 Babel 這些工具都是基于插件的。愛(ài)掏網(wǎng) - it200.com基本的工作流程就是讀取文件,解析代碼成 AST,調(diào)用插件去轉(zhuǎn)換 AST,最后生成代碼。愛(ài)掏網(wǎng) - it200.com要了解 Webpack 的原理,就要知道 Webpack 基于一個(gè)叫 tapable 的模塊系統(tǒng)。愛(ài)掏網(wǎng) - it200.com
那我們要如何了解這些呢?要了解這些,可以去各大網(wǎng)站和博客上的《XXX源碼解析》系列。愛(ài)掏網(wǎng) - it200.com通過(guò)這些文章,我們可以對(duì)我們要看的框架/工具的原理有一個(gè)大致的了解。愛(ài)掏網(wǎng) - it200.com
本地build
不過(guò)最終我們還是要直接看源碼。愛(ài)掏網(wǎng) - it200.com筆者真正看源碼的第一步就是把項(xiàng)目的代碼倉(cāng)庫(kù) clone 到本地。愛(ài)掏網(wǎng) - it200.com然后按項(xiàng)目 README 上的構(gòu)建指南,在本地 build 一下。愛(ài)掏網(wǎng) - it200.com
如果是前端框架,我們可以在 HTML 中里直接引入本地 build 出的 umd bundle(記得用 development build,不然會(huì)把代碼壓縮,可讀性差),然后寫(xiě)一個(gè)簡(jiǎn)單的 demo,demo 里引入本地的 build。愛(ài)掏網(wǎng) - it200.com如果是基于 Nodejs 的工具,我們可以用 npm link 把這個(gè)工具的命令 link 到本地。愛(ài)掏網(wǎng) - it200.com也可以直接看項(xiàng)目的 package.json 的入口文件,直接用 node 運(yùn)行那個(gè)文件。愛(ài)掏網(wǎng) - it200.com
這里要強(qiáng)調(diào)一下,大型的開(kāi)源項(xiàng)目一般都會(huì)有一個(gè) Contribution Guide,目的是讓想貢獻(xiàn)代碼的開(kāi)發(fā)者更快上手。愛(ài)掏網(wǎng) - it200.com里面就有講怎么在本地構(gòu)建代碼。愛(ài)掏網(wǎng) - it200.com
以 React 為例,React 的 Contributing Guide 里就 Development Workflow 這一節(jié)。愛(ài)掏網(wǎng) - it200.com里面有這么一段話:
The easiest way to try your changes is to run yarn build core,dom --type=UMD and then open fixtures/packaging/babel-standalone/dev.html. This file already uses react.development.js from the build folder so it will pick up your changes.
所以 React 倉(cāng)庫(kù)中的 fixtures/packaging/babel-standalone/dev.html 就是一個(gè)方便的 demo 頁(yè)。愛(ài)掏網(wǎng) - it200.com我們可以在這個(gè)頁(yè)面快速查看我們?cè)诒镜貙?duì)代碼的改動(dòng)。愛(ài)掏網(wǎng) - it200.com
你可以嘗試著在項(xiàng)目的入口文件中加入一句 log,看看是不是可以在控制臺(tái)/終端看到這句 log。愛(ài)掏網(wǎng) - it200.com如果可以的話,恭喜你,你現(xiàn)在可以隨便把玩這個(gè)項(xiàng)目了!
理清目錄結(jié)構(gòu)
在看具體的代碼之前,我們需要理清項(xiàng)目的目錄結(jié)構(gòu),這樣我們才能更快的知道在哪里地方找相關(guān)功能的代碼。愛(ài)掏網(wǎng) - it200.com
我們看看 React 的目錄結(jié)構(gòu)。愛(ài)掏網(wǎng) - it200.comReact 是一個(gè) monorepo。愛(ài)掏網(wǎng) - it200.com也就是一個(gè)倉(cāng)庫(kù)里包含了多個(gè)子倉(cāng)庫(kù)。愛(ài)掏網(wǎng) - it200.com我們?cè)?packages 目錄下可以看到很多單獨(dú)的 package:
在 React 16 之后,React 的代碼分為 React Core,Renderer 和 Reconciler 三部分。愛(ài)掏網(wǎng) - it200.com這是因?yàn)?React 的設(shè)計(jì)讓我們可以把負(fù)責(zé)映射數(shù)據(jù)到 UI 的 Reconciler 以及負(fù)責(zé)渲染 Vritual DOM 到各個(gè)終端的 Renderer 和 React Core 分開(kāi)。愛(ài)掏網(wǎng) - it200.comReact Core 包含了 React 的類(lèi)定義和一些頂級(jí) API。愛(ài)掏網(wǎng) - it200.com大部分的渲染和 View 層 diff 的邏輯都在 Reconciler 和 Renderer 中。愛(ài)掏網(wǎng) - it200.com
Babel 也是一個(gè) monorepo。愛(ài)掏網(wǎng) - it200.comBabel 的核心代碼是 babel-core 這個(gè) package,Babel 開(kāi)放了接口,讓我們可以自定義 Visitor,在AST轉(zhuǎn)換時(shí)被調(diào)用。愛(ài)掏網(wǎng) - it200.com所以 Babel 的倉(cāng)庫(kù)中還包括了很多插件,真正實(shí)現(xiàn)語(yǔ)法轉(zhuǎn)換的其實(shí)是這些插件,而不是 babel-core 本身。愛(ài)掏網(wǎng) - it200.com
Vuejs 的代碼比較典型,核心代碼在 src 目錄下,按功能模塊劃分。愛(ài)掏網(wǎng) - it200.com因?yàn)?Vue 也支持多平臺(tái)渲染,所以把平臺(tái)相關(guān)的代碼都放到了 platform 文件夾下,core 文件夾中是 Vue 的核心代碼,compiler 是 Vue 的模板編譯器,把 HTML 風(fēng)格的模板編譯為 render function。愛(ài)掏網(wǎng) - it200.com
Webpack 和 Babel 一樣,可以說(shuō)都是基于插件的系統(tǒng)。愛(ài)掏網(wǎng) - it200.comWebpack 的主要源碼在 lib 目錄下,里面的 webpack.js 就是入口文件。愛(ài)掏網(wǎng) - it200.com
上面說(shuō)了四個(gè)項(xiàng)目的目錄結(jié)構(gòu),那我們遇到一個(gè)新的開(kāi)源項(xiàng)目,應(yīng)該怎么了解它的目錄結(jié)構(gòu)呢?
如果這個(gè)項(xiàng)目是一個(gè) monorepo,首先我們要找到核心的那個(gè) package,然后看里面的代碼。愛(ài)掏網(wǎng) - it200.com
不是 monorepo 的話,一般來(lái)說(shuō),如果這個(gè)項(xiàng)目是一個(gè) CLI 的工具,那 bin 目錄下放的就是命令行界面相關(guān)的入口文件,lib 或者 src 下面就是工具的核心代碼。愛(ài)掏網(wǎng) - it200.com如果這個(gè)項(xiàng)目是一個(gè)前端 View 層框架,那目錄結(jié)構(gòu)就和 Vue 類(lèi)似。愛(ài)掏網(wǎng) - it200.com
作為驗(yàn)證,大家可以看一下打包工具 parcel 和前端 View 層庫(kù) moon 的目錄結(jié)構(gòu)。愛(ài)掏網(wǎng) - it200.com目錄結(jié)構(gòu)這個(gè)東西往往是大同小異,多看幾個(gè)項(xiàng)目就熟悉了。愛(ài)掏網(wǎng) - it200.com
debugger && 全局搜索大法
運(yùn)行了本地的 build,了解了目錄結(jié)構(gòu),接下來(lái)我們就可以開(kāi)始看源碼了!之前說(shuō)了,我們要以問(wèn)題驅(qū)動(dòng),下面我就以 React 調(diào)用 setState 前后發(fā)生了什么這個(gè)問(wèn)題作為例子。愛(ài)掏網(wǎng) - it200.com
我們可以在 setState 的地方打一個(gè)斷點(diǎn)。愛(ài)掏網(wǎng) - it200.com首先我們要找到 setState 在什么地方。愛(ài)掏網(wǎng) - it200.com這個(gè)時(shí)候之前的準(zhǔn)備工作就派上用處了。愛(ài)掏網(wǎng) - it200.com我們知道 React 的共有 API 在 react 這個(gè) package 下面。愛(ài)掏網(wǎng) - it200.com我們就在那個(gè) package 里面全局搜索。愛(ài)掏網(wǎng) - it200.com我們發(fā)現(xiàn)這個(gè) API 定義在 src/ReactBaseClasses.js 這個(gè)文件里。愛(ài)掏網(wǎng) - it200.com
于是我們就在這里打一個(gè)斷點(diǎn):
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
debugger;
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
復(fù)制代碼
然后運(yùn)行本地 React build 的 demo 頁(yè)面,讓組件觸發(fā) setState,我們就可以在 Devtool 里看到斷點(diǎn)了。愛(ài)掏網(wǎng) - it200.com
我們走進(jìn) this.updater.enqueueSetState 這個(gè)調(diào)用,就來(lái)到了 ReactFiberClassComponent 這個(gè)函數(shù)中的 enqueueSetState,這里調(diào)用了 enqueueUpdate 和 scheduleWork 兩個(gè)函數(shù),如果要深入 setState 之后的流程,我們只需要再點(diǎn)擊
如果想看 setState 之前發(fā)生了什么,我們只需要看 Devtool 右邊的調(diào)用棧:
點(diǎn)擊每一個(gè) frame 就可以跳到對(duì)應(yīng)的函數(shù)中,并且恢復(fù)當(dāng)時(shí)的上下文。愛(ài)掏網(wǎng) - it200.com
結(jié)合一步一步的代碼調(diào)試,我們可以看到框架的函數(shù)調(diào)用棧。愛(ài)掏網(wǎng) - it200.com對(duì)于每個(gè)重要的函數(shù),我們可以在倉(cāng)庫(kù)里搜索到源碼,進(jìn)一步研究。愛(ài)掏網(wǎng) - it200.com
Node 工具的調(diào)試方法也是相似的,我們可以在運(yùn)行 node 命令時(shí)加上 --inspect 參數(shù)。愛(ài)掏網(wǎng) - it200.com具體可以看 Debugging Node.js with Chrome DevTools 這篇博客。愛(ài)掏網(wǎng) - it200.com
其實(shí)大家都知道單步調(diào)試這種辦法,但在哪里打斷點(diǎn)才是最關(guān)鍵的。愛(ài)掏網(wǎng) - it200.com我們?cè)谑煜た蚣艿脑碇?,就可以在框架?strong>關(guān)鍵鏈路上打斷點(diǎn),比如前端 View 層框架的聲明周期鉤子和 render 方法,Node 工具的插件函數(shù),這些代碼都是框架運(yùn)行的必經(jīng)之地,是不錯(cuò)的切入點(diǎn)。愛(ài)掏網(wǎng) - it200.com
如果是為了了解一個(gè)特定的問(wèn)題,大家可以直接在自己覺(jué)得有問(wèn)題的地方打斷點(diǎn)。愛(ài)掏網(wǎng) - it200.com然后把源碼運(yùn)行起來(lái),想辦法讓代碼運(yùn)行到那個(gè)地方。愛(ài)掏網(wǎng) - it200.com我們?cè)跀帱c(diǎn)可以看到局部變量等等信息,有助于定位問(wèn)題。愛(ài)掏網(wǎng) - it200.com
來(lái)自開(kāi)發(fā)團(tuán)隊(duì)的資源
其實(shí)開(kāi)源項(xiàng)目的開(kāi)發(fā)團(tuán)隊(duì)也都致力于讓更多的人參與到項(xiàng)目中來(lái),降低項(xiàng)目的門(mén)檻。愛(ài)掏網(wǎng) - it200.com所以我們?cè)诰€上其實(shí)可以找到很多來(lái)自開(kāi)發(fā)團(tuán)隊(duì)的資源。愛(ài)掏網(wǎng) - it200.com這些資源可以幫助我們?nèi)ダ斫忭?xiàng)目的原理。愛(ài)掏網(wǎng) - it200.com
關(guān)注核心開(kāi)發(fā)者
每個(gè)項(xiàng)目都有一些核心開(kāi)發(fā)者,比如 React 的 Dan Abramov, Andrew Clark 和 Sebastian Markb?ge。愛(ài)掏網(wǎng) - it200.comWebpack 的 Tobias Koppers 和 Sean Larkin。愛(ài)掏網(wǎng) - it200.comVue 的 Evan You。愛(ài)掏網(wǎng) - it200.com我們可以在 Twitter 上關(guān)注他們,了解項(xiàng)目的動(dòng)態(tài)。愛(ài)掏網(wǎng) - it200.com
關(guān)注官方博客和演講視頻
如果我們關(guān)注了上面的核心開(kāi)發(fā)者,就會(huì)發(fā)現(xiàn)他們時(shí)常會(huì)發(fā)布一些和源碼/項(xiàng)目原理有關(guān)的博客或者視頻。愛(ài)掏網(wǎng) - it200.com
React 的官方博客最近就有很多和項(xiàng)目開(kāi)發(fā)有關(guān)的博客。愛(ài)掏網(wǎng) - it200.com
- Behind the Scenes: Improving the Repository Infrastructure 這篇介紹的是 React 項(xiàng)目倉(cāng)庫(kù)的基礎(chǔ)設(shè)施。愛(ài)掏網(wǎng) - it200.com
- Sneak Peek: Beyond React 16
Andrew Clark 一開(kāi)始就寫(xiě)了一篇介紹 fiber 架構(gòu)的文檔。愛(ài)掏網(wǎng) - it200.com Dan Abramov 最近在 JSConf 上對(duì) React 未來(lái)的一些新特性的介紹 - Beyond React 16。愛(ài)掏網(wǎng) - it200.comReact 博客中的 Sneak Peek: Beyond React 16 也是對(duì)這次 Talk 的介紹。愛(ài)掏網(wǎng) - it200.com
Evan You 介紹前端框架數(shù)據(jù)變化偵測(cè)原理的 Talk。愛(ài)掏網(wǎng) - it200.comVue 文檔中也有 Reactivity in Depth 這樣的介紹原理的章節(jié)。愛(ài)掏網(wǎng) - it200.com
Sean Larkin 的 Everything is a plugin! Mastering webpack from the inside out 介紹了 Webpack 的核心組件 Tapable。愛(ài)掏網(wǎng) - it200.com
James Kyle 的 How to Build a Compiler 可以讓我們了解 Babel 轉(zhuǎn)譯代碼的基本流程。愛(ài)掏網(wǎng) - it200.com
寫(xiě)在最后
本文最核心的觀點(diǎn)就是,看源碼的目的是為了解決問(wèn)題。愛(ài)掏網(wǎng) - it200.com我們鼓勵(lì)大家在本地把大型項(xiàng)目的源碼跑起來(lái),自己隨意把玩,研究。愛(ài)掏網(wǎng) - it200.com因?yàn)樵创a也是普通的代碼,并沒(méi)有太多門(mén)檻。愛(ài)掏網(wǎng) - it200.com唯一的門(mén)檻可能就來(lái)源于開(kāi)源項(xiàng)目作者和普通開(kāi)發(fā)者之間的信息不對(duì)稱(chēng),普通開(kāi)發(fā)者對(duì)項(xiàng)目的原理和目錄結(jié)構(gòu)不夠了解。愛(ài)掏網(wǎng) - it200.com
我們可以從開(kāi)發(fā)者那里獲取資源,同時(shí)也可以多閱讀社區(qū)里的源碼分析文章,這些都有助于我們理解項(xiàng)目的原理,為后續(xù)的源碼分析打下基礎(chǔ)。愛(ài)掏網(wǎng) - it200.com
作者:螞蟻金服數(shù)據(jù)體驗(yàn)技術(shù)
鏈接:https://juejin.im/post/5afe3735518825426539afce
來(lái)源:掘金
著作權(quán)歸作者所有。愛(ài)掏網(wǎng) - it200.com商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。愛(ài)掏網(wǎng) - it200.com