<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何閱讀源碼 —— 以 Vetur 為例

          共 19360字,需瀏覽 39分鐘

           ·

          2021-08-25 09:52

          全文近萬字...來都來了,點(diǎn)個(gè)贊再走吧

          我很早就意識到,能熟練、高效閱讀開源前端框架源碼是成為一個(gè)高級前端工程師必須具備的基本技能之一,所以在我職業(yè)生涯的最早期,就已經(jīng)開始做了很多次相關(guān)的嘗試,但結(jié)果通常都以失敗告終,原因五花八門:

          • 缺乏必要的背景知識,猶如閱讀天書
          • 不理解項(xiàng)目架構(gòu)、設(shè)計(jì)理念,始終不得要領(lǐng)
          • 目標(biāo)不夠聚焦,閱讀過程容易復(fù)雜化
          • 容易陷入細(xì)節(jié),在不重要的問題上糾結(jié)半天
          • 容易追著分支流程跑,分散注意力
          • 沒有及時(shí)記錄筆記和總結(jié),沒有把知識碾碎、重組、內(nèi)化成自己的東西
          • 沒有處理過特別復(fù)雜問題的經(jīng)歷,潛在的不自信心理
          • 個(gè)人毅力、韌性不足,或者目標(biāo)感不夠強(qiáng)烈,遇到困難容易放棄
          • 等等

          這個(gè)列表還可以繼續(xù)往下拉很長很長,總之既有我自己主觀認(rèn)知上的限制又有切切實(shí)實(shí)的客觀原因。后來因?yàn)楣ぷ鞯钠鯔C(jī)硬著頭皮看完 Vue 和 mxGraph 的源碼,發(fā)現(xiàn)事情并沒有自己想象中那么困難,后來前前后后陸續(xù)看了很多框架源碼,包括 Webpack、Webpack-sources、Vite、Eslint、Babel、Vue-cli、Vuex、Uniapp、Lodash、Vetur、Echarts、Emmet 等等,愚鈍如我也慢慢摸索出了一些普適的方式方法,進(jìn)而斗膽撰下這篇文章,不敢說授人以漁,但至少也該拋磚引玉吧。

          所以這是一篇為哪些有意,或準(zhǔn)備,或已經(jīng)在閱讀前端框架源碼的同學(xué)而寫的文章,我會(huì)在這里拋出一些經(jīng)過我個(gè)人多次實(shí)踐總結(jié)出來的閱讀技巧和原則,并結(jié)合 Vetur 源碼,具體地講解我在閱讀源碼的各個(gè)階段所思所想,希望能給讀者帶來一些啟發(fā)。

          弄清楚目標(biāo)

          在介紹具體的技巧之前,有必要先跟讀者探討一下閱讀源碼的動(dòng)機(jī),想清楚到底需不需要通過這種方式提升自身技能,雖然學(xué)習(xí)優(yōu)秀框架源碼確實(shí)有非常多不言自明的好處,但每個(gè)人的經(jīng)驗(yàn)、所處的語境、訴求、思維習(xí)慣不同,實(shí)際學(xué)習(xí)效果在不同個(gè)體或個(gè)體的不同時(shí)期必然存在極大的差異,這里面最大的變量一是經(jīng)驗(yàn),二是目標(biāo),經(jīng)驗(yàn)因人而異,且很難在短時(shí)間內(nèi)補(bǔ)齊,沒有太多討論空間;倒是目標(biāo)方面值得盤道盤道。

          第一層,先弄清楚為啥要閱讀源碼?可能的原因有很多,例如:

          • 為了增進(jìn)對框架的認(rèn)知深度,提升個(gè)人能力
          • 為了應(yīng)對面試
          • 為了解決當(dāng)下某個(gè)棘手的 bug 或性能問題
          • 基于某些原因,需要對框架做二次改造
          • 反正閑著,也不知道該學(xué)點(diǎn)啥,試試唄。。。
          • 好奇

          這里面有一些很抽象,例如最后一個(gè)“好奇”;有一些很具體,例如“為了做二次改造”;還有一些在具體與抽象之間。按照 SMART 原則的說法,越具體、可衡量的目標(biāo)越容易達(dá)成,如果讀者的目標(biāo)還處在比較模棱兩可,不夠具體詳細(xì)的階段,那執(zhí)行過程大概率會(huì)翻車,畢竟這是一件特別消耗精力與耐性的活兒。

          對于這種情況,我的建議是不妨往更細(xì)節(jié)的層次再想一想,例如對于最后一點(diǎn)“好奇”,可以想想具體有哪些特性讓你特別神奇,值得花時(shí)間精力去細(xì)致地探索,放在 Vetur 語境下可以是“我想了解 Vetur 的 template 錯(cuò)誤提示與 eslint 如何結(jié)合在一起,實(shí)現(xiàn)模板層面的錯(cuò)誤提示功能”,這就很具體很容易衡量了。

          第二層,讀者如果已經(jīng)有了明確、具體、可衡量的目標(biāo),不妨在開始之前先自問幾個(gè)問題:

          • 當(dāng)下確實(shí)需要以閱讀源碼的方式增進(jìn)自己對框架的認(rèn)知深度嗎?有沒有一些更輕量級,迭代速度更快的學(xué)習(xí)方式?
          • 你所選定的框架,其復(fù)雜度、技術(shù)難度是否與你當(dāng)下的能力匹配?最好的狀態(tài)是你自認(rèn)為踮踮腳就能夠到,過高,不具有可行性;過低,ROI 不值當(dāng)。

          如果經(jīng)過這番推敲之后,必要性、可行性、相關(guān)性都與個(gè)人目標(biāo)契合,那就沒啥可猶豫的。

          第三層,需要辯證地去看待所謂“目標(biāo)” —— 不是把整個(gè)項(xiàng)目完整讀完讀通才叫成功,如果能從一些語句、片段、局部模塊中習(xí)得新的設(shè)計(jì)思維、工具方法,甚至僅僅是命名規(guī)范都可以算作個(gè)人的一點(diǎn)進(jìn)步,積少成多遠(yuǎn)比拔苗助長靠譜的多。所以一開始沒必要把目標(biāo)定的太高,能剛剛好滿足自身需求是最好的,過程中如果發(fā)現(xiàn)問題域的復(fù)雜度在不斷膨脹變大,持續(xù)投入很多時(shí)間卻始終沒有明顯成效的話,那建議果斷放棄或者請求外援,重新評估目標(biāo)與可行性之后再做決定。

          總之,這是一個(gè)預(yù)期管理的問題,我們可以多參考 SMART 原則,多從具體、可衡量、可行性、相關(guān)性幾個(gè)維度思考,不斷推敲是否需要做這件事;如何拆解目標(biāo),用目標(biāo)反推計(jì)劃,不斷推進(jìn)個(gè)人成功。

          閱讀技巧

          了解背景知識

          「知識」是形成「理解」的必要條件,展開學(xué)習(xí)任何一個(gè)開源項(xiàng)目之前都有必要花點(diǎn)時(shí)間調(diào)研項(xiàng)目相關(guān)的基礎(chǔ)知識,漸進(jìn)構(gòu)建起一套屬于你自己的知識體系,這包括:

          • 優(yōu)質(zhì)參考資料 —— 收集一波質(zhì)量較高的學(xué)習(xí)資料,收集過程可以同步通讀一遍
          • 框架是如何運(yùn)行的 —— 也就是所謂的入口
          • IO —— 框架如何與外部交互?它通常接受什么形態(tài)的運(yùn)行參數(shù)?輸出什么形式的結(jié)果?
          • 生態(tài) —— 優(yōu)秀的框架背后通常都帶有一套成熟的生態(tài)系統(tǒng),例如 Vue,框架衍生品如何補(bǔ)齊框架本身的功能缺失?它們以何種方式,以什么樣的 IO 與主框架交互?遵循怎么樣的寫法規(guī)則?
          • 如何斷點(diǎn)調(diào)試 —— 這幾乎是最有效的分析方法,斷點(diǎn)調(diào)試能夠幫助你細(xì)致地了解每一行代碼的作用。

          注意,這里的目標(biāo)是迅速構(gòu)建起關(guān)于這個(gè)開源項(xiàng)目的抽象 —— 甚至不太準(zhǔn)確的知識框架,有意思地避免陷入無盡的細(xì)節(jié)中,就像在閱讀一篇文章的時(shí)候,可以先看看目錄結(jié)構(gòu)粗略地了解文章的信息框架,了解文章大概內(nèi)容。

          例如,我剛開始學(xué)習(xí) Vetur 的時(shí)候只知道這是一個(gè) VS Code 插件,但完全不了解插件怎么寫、怎么運(yùn)行、怎么實(shí)現(xiàn)語言特性,所以我做的第一件事情是仔仔細(xì)細(xì)閱讀 VS Code 的官方文檔(所幸文檔非常齊全,不像某著名打包工具),學(xué)習(xí)關(guān)于插件開發(fā)的基本知識,包括:

          進(jìn)一步總結(jié)關(guān)于 VS Code 語言插件的要素:

          • 「怎么寫插件」:通過 package.json 文件的 contributesmain 等屬性,聲明插件的功能與入口
          • 「怎么運(yùn)行」:開發(fā)階段使用 F5 啟動(dòng)調(diào)試
          • 「怎么編寫語言特性」:使用 「詞法高亮、Language API、Language Server Protocol」 三類技術(shù)實(shí)現(xiàn)

          VS Code 領(lǐng)域的知識量還是很龐大的,學(xué)習(xí)背景知識并梳理成這種高度結(jié)構(gòu)化、高度抽象的腦圖能夠給你一個(gè)更高層、全面的視角,理想狀態(tài)下,后續(xù)實(shí)際分析源碼的時(shí)候這些骨架脈絡(luò)能夠讓你非常本能地映射到某一個(gè)切面的知識點(diǎn),事半功倍。

          六步循環(huán)分析

          接下來,我會(huì)介紹一套我常用的分析流程:

          整體分為六個(gè)步驟:

          • 理解項(xiàng)目結(jié)構(gòu)
          • 尋找合適的切入點(diǎn)
          • 就著切入點(diǎn)查閱文章資料
          • 就著切入點(diǎn)分析代碼流程
          • 局部深入研究
          • 及時(shí)總結(jié)
          • 之后,再繼續(xù)設(shè)定切入點(diǎn),重復(fù)執(zhí)行上述流程直到透徹地理解了問題

          這是一套在 「總-分-總」 視角之間反復(fù)橫跳最終構(gòu)建出完整視角的方法論,重點(diǎn)就在于告訴讀者在什么階段應(yīng)該關(guān)注什么,忽略什么,輸入什么,輸出什么,我個(gè)人就是按照這個(gè)方法慢慢摸索出包括 Webpack、Babel、Vue、Vetur、mxGraph 在內(nèi)的各種開源框架的實(shí)現(xiàn)原理。

          理解項(xiàng)目結(jié)構(gòu)

          剛開始閱讀源碼的時(shí)候,相信大多數(shù)人都會(huì)很懵逼,無從下手,這是因?yàn)樽x者對項(xiàng)目缺乏一個(gè)必要的框架性認(rèn)知,不了解程序的入口在哪里、關(guān)鍵組件有哪些、各個(gè)文件夾有什么作用等,遇到問題無法迅速推測實(shí)現(xiàn)路徑。

          所以,閱讀源碼的第一個(gè)步驟,應(yīng)該是先花點(diǎn)時(shí)間粗淺地分析、理解項(xiàng)目的組織結(jié)構(gòu)。所幸一個(gè)值得深入閱讀學(xué)習(xí)的開源項(xiàng)目,通常都會(huì)有較強(qiáng)的整體性與一致性,我們只需要梳理出三條線索:

          • 分析項(xiàng)目入口
          • 分析項(xiàng)目依賴了哪些基礎(chǔ)工具,包括編譯工具,如 webpack、Typescript、babel;基礎(chǔ)庫,如 lodash、tapable、snabbdom。
          • 將項(xiàng)目中重要文件夾、文件逐一列舉出來,理解它們?nèi)绾伟凑找蕾囮P(guān)系組成一個(gè)整體的架構(gòu)。

          放在 vetur 語境下,我們在上面“了解背景知識”一節(jié)已經(jīng)了解到 VS Code 插件需要在 package.json 文件通過 contributes 等屬性聲明插件的配置信息,所以這幾個(gè)問題都能在 package.json 文件找到答案。

          入口分析

          首先,需要識別出 Vetur 應(yīng)用的入口,這一步的作用是幫助我們理解 Vetur 是如何向 VS Code 貢獻(xiàn)新特性的。分析 vetur 的 package.json 發(fā)現(xiàn)有三種直接指向到文件的配置項(xiàng):

          • contributes.languages 指定語言配置文件
          • contributes.grammars 指定語法配置文件
          • "main": "./dist/vueMain.js" 指定插件執(zhí)行入口

          三個(gè)入口分別實(shí)現(xiàn)三種不同的語言特性功能,略顯復(fù)雜,這里有必要分別展開了解一下。

          探索 contributes.languages 配置

          逐個(gè)講解,contributes.languages 配置信息指向到 ./languages/***-language-configuration.json 文件,如:

          {
              // ...
              "contributes": {
                  "languages": [
                      {
                          "id""vue",
                          "configuration""./languages/vue-language-configuration.json"
                      },
                      {
                          "id""vue-html",
                          "configuration""./languages/vue-html-language-configuration.json"
                      }
                      // ...
                  ]
              }
              // ...
          }

          這里回過頭翻一下 VS Code 對 [contributes.languages](https://code.visualstudio.com/api/references/contribution-points#contributes.languages) 的解釋(感謝資源豐富的 VS Code 社區(qū)):

          ?

          Contribute definition of a language. This will introduce a new language or enrich the knowledge VS Code has about a language.

          ?

          大意是說 contributes.languages 配置項(xiàng)的作用主要是增進(jìn) VS Code 對具體語言的理解,至于怎么增強(qiáng)呢?繼續(xù)打開配置項(xiàng)中的 ./languages/vue-language-configuration.json 文件:

          {
              "comments": {
                  // symbol used for single line comment. Remove this entry if your language does not support line comments
                  "lineComment""http://",
                  // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
                  "blockComment": [
                      "/*",
                      "*/"
                  ]
              },
              // ...
          }

          文件中定義了行內(nèi) comment、塊級 comment、括號、折疊等語言規(guī)則的配置,規(guī)則都很簡單直白,篇幅關(guān)系這里不展開。

          回顧一下探索步驟:

          • 翻閱參考資料,理解 contributes.languages 配置的作用
          • 打開對應(yīng)入口文件,猜測各個(gè)配置項(xiàng)的作用
          • 繼續(xù)翻閱參考資料,或者修改配置,驗(yàn)證猜想
          探索 contributes.grammars 配置

          contributes.grammars 項(xiàng)包含諸多指向到 ./syntaxes/vue-xxx.json 的配置信息,形如:

          {
              "contributes": {
                "grammars": [
                  {
                    "language""vue",
                    "scopeName""source.vue",
                    "path""./syntaxes/vue-generated.json",
                    "embeddedLanguages": {
                      "text.html.basic""html",
                      // ...
                    }
                  },
                  {
                    "language""vue-postcss",
                    "scopeName""source.css.postcss",
                    "path""./syntaxes/vue-postcss.json"
                  }
                  // ...
                ]
              }
            }
            

          同樣的,我們先查一下官網(wǎng)對 [contributes.grammars](https://code.visualstudio.com/api/references/contribution-points#contributes.grammars) 配置項(xiàng)的解釋:

          ?

          Contribute a TextMate grammar to a language. You must provide the language this grammar applies to, the TextMate scopeName for the grammar and the file path.

          ?

          這段描述略微復(fù)雜,大意是開發(fā)者可以通過 grammars 屬性提供關(guān)于語言的 TextMate 形式的語法描述,grammars 配置項(xiàng)包含三個(gè)屬性:

          • language:語言的名稱
          • scopeName:語言的分類,與 TextMate scopeName 同義,可用于嵌套語法定義
          • path:語言的詞法規(guī)則文件

          這里面 path 屬性指向一個(gè)內(nèi)容更復(fù)雜的配置文件 ./syntaxes/vue-xxx.json,我們可以接著打開其中任意一個(gè)文件,關(guān)鍵內(nèi)容結(jié)構(gòu)如下:

          {
              "name""Vue HTML",
              "scopeName""text.html.vue-html",
              "fileTypes": [],
              "uuid""ca2e4260-5d62-45bf-8cf1-d8b5cc19c8f8",
              "patterns": [
                  // ...
                  {
                      "name""meta.tag.any.html",
                      "begin""(<)([A-Z][a-zA-Z0-9:-]*)(?=[^>]*></\\2>)",
                      "beginCaptures": {
                          "1": {
                              "name""punctuation.definition.tag.begin.html"
                          },
                          "2": {
                              "name""support.class.component.html"
                          }
                      }
                  }
              ],
              "repository": {
                  // ...
              }
          }

          按照 Syntax Highlight Guide(https://zjsms.com/e7E5Jdq/) 一節(jié)的說法這里面最重要的是 patterns 屬性,而 patterns 屬性最關(guān)鍵的功能就是以正則語句表達(dá)語言的詞法分析規(guī)則,并分配詞法對應(yīng)的 name 命名,詳細(xì)的配置規(guī)則還可以繼續(xù)參考 TextMate 官網(wǎng),這里大致理解作用即可,先不展開深究。

          探索 main 配置

          接著往下看,第三個(gè)值得關(guān)注的是 main 屬性,在 vetur 中對應(yīng)的值為:

          "main""./dist/vueMain.js"

          VS Code 官網(wǎng)對 main 屬性的解釋非常精簡:「The entry point to your extension」,也就是插件的入口,通常需要指向到可執(zhí)行的 JS 文件,插件啟動(dòng)時(shí) VS Code 會(huì)執(zhí)行這個(gè)入口文件導(dǎo)出的 activate 方法,內(nèi)容框架大致為:

          import vscode from 'vscode';

          export async function activate(context: vscode.ExtensionContext{
              // ... 啟動(dòng)邏輯
          }

          在 Vetur 中,activate 函數(shù)定義在 client/vueMain.ts 文件,分析源碼可知該函數(shù)主要完成如下事項(xiàng):

          • 調(diào)用 registerXXXCommands 方法注冊一系列命令
          • 調(diào)用 initializeLanguageClient 方法初始化 LSP Client 對象

          這兩個(gè)操作具體的作用,我們先按下不表,后面再展開。

          小結(jié)

          對入口的分析就到這里了,我們先總結(jié)、記錄下關(guān)鍵信息:

          • Vetur 本質(zhì)上是一個(gè) VS Code 插件,所有配置 —— 包括入口都記錄在 package.json 文件中
          • Vetur 包含三種啟動(dòng)入口:
            • contributes.languages:定義一些簡單的語言基本配置,包括怎么折疊,怎么注釋
            • contributes.grammars:定義了一套基于 TextMate 引擎的詞法規(guī)則,用于實(shí)現(xiàn)代碼高亮
            • main:定義了插件的啟動(dòng)入口,入口中注冊了一系列命令,同時(shí)創(chuàng)建了基于 LSP 協(xié)議的 Language Client 對象,而 LSP 協(xié)議用于實(shí)現(xiàn)如代碼補(bǔ)全、錯(cuò)誤診斷、跳轉(zhuǎn)定義等高級特性

          到這里,雖然我們還是不了解 Vetur 的實(shí)現(xiàn)細(xì)節(jié),但是對 Vetur 的背景知識與項(xiàng)目結(jié)構(gòu)應(yīng)該已經(jīng)有了一個(gè)比較基礎(chǔ)的認(rèn)知,已經(jīng)能大致識別哪些功能由哪些模塊實(shí)現(xiàn)。

          OK,這里先保持好這個(gè)模模糊糊的認(rèn)知就行了,不要花太多時(shí)間。

          基礎(chǔ)依賴分析

          接下來,需要梳理一下 Vetur 的基礎(chǔ)依賴,這一步的作用是幫助我們理解 Vetur 可能用到哪些基礎(chǔ)技術(shù),比如用到哪些工程化工具、怎么編譯、怎么檢查代碼等。

          Vetur 的 package.json 文件主要包含三類信息:

          • VS Code 插件配置信息,大體上在上一節(jié)都有描述,這里不展開
          • 工程化命令,核心有:
            • watch:對應(yīng)命令為 rollup -c rollup.config.js -w ,由此可以推斷 Vetur 基于 Rollup 實(shí)現(xiàn)構(gòu)建
            • compile:功能與 watch 相似
            • lint:對應(yīng)命令為 tslint -c tslint.json **.ts ,由此可以推斷 Vetur 基于 tslint 實(shí)現(xiàn)代碼檢查
          • 項(xiàng)目的 devDependencies 依賴,主要包含 typescript、tslint、rollup、vscode-languageclient、husky、mocha、vscode-test、prettier

          那么,從這些信息我們基本可以推斷出如下信息:

          • Vetur 使用 Rollup + typescript 等工具執(zhí)行構(gòu)建工作,按常理執(zhí)行 yarn watch 命令應(yīng)該就能啟動(dòng)一個(gè)持續(xù)的構(gòu)建工作進(jìn)程
          • Vetur 使用 tslint 實(shí)現(xiàn)代碼檢查,配合 huscky + prettier 完成格式化工作
          • Vetur 使用 mocha + vscode-test 實(shí)現(xiàn)自動(dòng)化測試

          文件結(jié)構(gòu)

          接著,還需要稍微展開看看 Vetur 的文件結(jié)構(gòu),這一步能夠一定程度上幫助我們理解 Vetur 的代碼架構(gòu)及要素,推測各種特性是在什么位置實(shí)現(xiàn)的。Vetur 的文件結(jié)構(gòu)大致上如下:

          vetur
          ├─ .vscode
          │  ├─ ...
          ├─ build
          │  ├─ ...
          ├─ client
          │  ├─ client.ts
          │  ├─ commands
          │  │  ├─ ...
          │  ├─ grammar.ts
          │  ├─ ...
          ├─ languages
          │  ├─ vue-html-language-configuration.json
          │  ├─ ...
          ├─ scripts
          │  ├─ build_grammar.ts
          │  └─ tsconfig.json
          ├─ server
          │  ├─ .gitignore
          │  ├─ .mocharc.yml
          │  ├─ .npmrc
          │  ├─ bin
          │  │  └─ vls
          │  ├─ package.json
          │  ├─ rollup.config.js
          │  ├─ src
          │  │  ├─ ...
          ├─ syntaxes
          │  ├─ markdown-vue.json
          │  ├─ pug
          │  │  ├─ ...
          │  ├─ ...
          │  └─ vue.yaml
          ├─ test
          │  ├─ ...
          ├─ vti
          │  ├─ README.md
          │  ├─ bin
          │  │  └─ vti
          │  ├─ package.json
          │  ├─ rollup.config.js
          │  ├─ src
          │  │  ├─ ...
          │  ├─ tsconfig.json
          │  └─ yarn.lock
          ├─ tsconfig.options.json
          ├─ package.json
          ├─ ...
          └─ yarn.lock

          其中,比較關(guān)鍵的有:

          • client:VS Code 插件的入口代碼,package.json 文件中 main 字段會(huì)指向這個(gè)目錄的產(chǎn)物
          • server:LSP 架構(gòu)中的 Server 端,上述 client 會(huì)通過 LSP 協(xié)議與這個(gè) server 目錄通信
          • syntaxes:Vetur 的詞法規(guī)則文件夾,內(nèi)部包含許多 JSON 格式,符合 TextMate 規(guī)則的詞法聲明
          • languages:Vetur 提供的語言配置信息,規(guī)則比較簡單,了解作用即可,不必深入
          • vti:按 vti/bin/vti 文件可以推斷,這里是 Vetur 的命令行工具,不在主流程內(nèi)可以先忽略
          • docs:按內(nèi)容可以推斷這是 Vetur 的介紹文檔,此處可忽略
          • build:構(gòu)建命令,package.json 文件的 script 命令有一些會(huì)指向這個(gè)目錄,可以忽略
          • 一系列基礎(chǔ)配置文件,包括 tsconfig.jsonpackage.json 等,可先忽略

          我們還可以繼續(xù)往下探索各個(gè)子目錄的內(nèi)容,但是注意淺嘗輒止即可,后面隨著源碼閱讀的深入,讀者對各個(gè)目錄的理解應(yīng)該會(huì)不斷迭代增長,現(xiàn)在沒必要花太多時(shí)間。

          小結(jié)

          回顧一下,我們首先學(xué)習(xí)了一些背景知識,之后花了一些時(shí)間分析項(xiàng)目的入口、基礎(chǔ)依賴、文件結(jié)構(gòu),到這里我們基本上可以推斷出:

          • Vetur 是一個(gè)語言插件,所以必然是使用 「詞法高亮、Language API、Language Server Protocol」 三類技術(shù)實(shí)現(xiàn)核心邏輯的,而 package.json 文件中的 contributes 配置項(xiàng)的內(nèi)容也恰好驗(yàn)證了這一點(diǎn)
          • 「詞法高亮」 相關(guān)的代碼集中在 syntaxes 文件夾
          • 「Language Server Protocol」 相關(guān)的代碼集中在 clientserver 文件夾
          • 可以用 yarn watch 命令持續(xù)構(gòu)建,配合 F5 快捷鍵啟動(dòng)調(diào)試

          這些信息是后續(xù)分析源碼的必要條件,而這個(gè)過程跟學(xué)習(xí)一門新語言很類似,讀者可以回想一下最開始學(xué)習(xí) JavaScript 的時(shí)候,有經(jīng)驗(yàn)的學(xué)習(xí)者不會(huì)一上來馬上深入諸如原型、變量提升、事件循環(huán)等語言細(xì)節(jié),而是先以更高層、更抽象的視角學(xué)習(xí) JavaScript 語言的基本骨架,包括函數(shù)、循環(huán)語句、分支判斷語句、對象等,從而構(gòu)建起一個(gè)抽象的結(jié)構(gòu)化認(rèn)知,后續(xù)再慢慢填充細(xì)節(jié),有點(diǎn)自頂向下的味道。

          設(shè)定切入點(diǎn)

          在對項(xiàng)目背景與結(jié)構(gòu)有基本了解之后,我們可以正式開始分析源碼了。首先,讀者要找到一個(gè)匹配自身狀態(tài)和需求的切入點(diǎn),本質(zhì)上就是將大目標(biāo)拆解成一系列小目標(biāo),將大問題拆解成一系列更具體的小問題,然后帶著具體問題更聚焦地去看代碼。

          所謂切入點(diǎn)可以直接對標(biāo)到框架的具體功能,或者某些底層機(jī)制的實(shí)現(xiàn)上,以 Vetur 為例,它實(shí)現(xiàn)了諸多輔助開發(fā) Vue SFC 組件的特性,包括代碼補(bǔ)全、錯(cuò)誤診斷、代碼高亮、跳轉(zhuǎn)到定義、hover 提示等等,這里面任意一個(gè)展開來都有大量可以挖掘的空間,如果從一開始就漫無目的瞎逛亂看那鐵定是看不出個(gè)所以然的,鑒于我的目標(biāo)就是想通過 Vetur 學(xué)習(xí) VS Code 插件的開發(fā)套路,所以選擇了一個(gè)看起來比較簡單的特性:「代碼補(bǔ)全」 作為第一個(gè)切入點(diǎn),后續(xù)的學(xué)習(xí)經(jīng)歷證明這是一個(gè)非常合適的點(diǎn),不復(fù)雜但是已經(jīng)能幫我窺見 Vetur 的核心工作機(jī)制,以此類推后面分析其它高級特性如代碼高亮、代碼補(bǔ)全等,基本上就是很輕車熟路的狀態(tài)了。

          如果你有一些更明確的目的,比如解決某個(gè)具體的 bug,那你應(yīng)該會(huì)更容易 get 到當(dāng)下最需要做的事情;如果始終抓不到要點(diǎn),那么建議先回到前面“了解背景知識”或“理解項(xiàng)目結(jié)構(gòu)”的步驟,繼續(xù)探索一些上下文信息,再試試問自己:我接下來到底應(yīng)該先了解哪些具體功能的實(shí)現(xiàn)邏輯?

          記住,這并不是一錘子買賣,如果你在后續(xù)的分析過程中發(fā)現(xiàn)這個(gè)切入點(diǎn)變得越來越復(fù)雜,超出最開始的預(yù)期,不要有心理負(fù)擔(dān),這再正常不過了,而且反而側(cè)面表現(xiàn)出你對問題域有越來越少的理解了,可以回過頭來重新調(diào)整目標(biāo),找一個(gè)更小的切入點(diǎn)。

          善用搜索引擎

          定下切入點(diǎn)后,首先要做的不是打開代碼咔咔就干,而應(yīng)該首先試試在社區(qū)搜索相關(guān)的資料,畢竟自媒體時(shí)代了,很多開源框架的知識已經(jīng)被無數(shù)人吃透、捏碎、重組成各種維度的文章,順著這些文章的思路去理解源碼會(huì)比完全靠自己摸索效率高很多。

          列舉幾種我常用的搜索渠道:

          • 谷歌 and 百度一類的搜索引擎,體感上谷歌的搜索質(zhì)量會(huì)好很多,不過有一定的英語門檻
          • 開源項(xiàng)目的官網(wǎng)、社區(qū)、wiki、github 等官方渠道,通常都會(huì)有比較不錯(cuò)的資料
          • Segmentfault、知乎、掘金、公眾號等垂直社區(qū)
          • 國外的 Medium/StackOverflow 社區(qū),質(zhì)量極高,很多大佬在上面活躍

          假如搜了一通找不到答案,可以試試不同的關(guān)鍵詞組合,我經(jīng)常用的關(guān)鍵詞有:

          • Xxx 源碼解析
          • Xxx 原理
          • 如何實(shí)現(xiàn) xxx

          假如還是找不到,還可以試試換一個(gè)意思接近的關(guān)鍵詞,繞點(diǎn)彎路。總之就是想盡辦法找到有用的,適合當(dāng)下問題的信息,幫助讀者更快更平滑地深入研究源碼,這一步對新手尤為重要。

          順便推銷一下我的公眾號:【Tecvan】,長期聚焦各類前端框架源碼的研讀解析,歡迎訂閱。

          分析關(guān)鍵流程

          前面說了一大通,到這里終于要開始正兒八經(jīng)地深入研究代碼了。

          其實(shí)源碼分析的過程特別像偵探電影,最開始你需要面對一堆凌亂,看起來相關(guān)又不太相關(guān)的線索,偵探需要從這千頭萬緒中找出唯一事實(shí)答案,這個(gè)過程通常有兩種行之有效的做法,一是自頂向下,從時(shí)間線、流程的角度出發(fā)歸納出大致的事件框架,之后再深入研究細(xì)節(jié),由抽象到具體;一是自底向上,找到疑點(diǎn)再往上逐層推敲,梳理出事件的全貌,由具體到抽象。

          兩種方式各有適用場景,如果出于學(xué)習(xí)目的,就是想了解某些功能特性的實(shí)現(xiàn)原理的話,就應(yīng)該自定向下,從應(yīng)用入口出發(fā)逐步向下跟蹤梳理出執(zhí)行流程,理解大框架之后深挖具體細(xì)節(jié);而如果是追查單一 bug 的時(shí)候就應(yīng)該找到出問題的地方,自底向上追溯出全貌再加以更改。

          我個(gè)人會(huì)更傾向于自頂向下的方式,例如我在學(xué)習(xí) Vetur 的時(shí)候,首先是選定了 「代碼補(bǔ)全」 這一類功能作為切入點(diǎn),之后從 server/main.ts 開始一路沿著主流程向下逐級探索,最終到達(dá)實(shí)際執(zhí)行代碼補(bǔ)全的位置,雖然實(shí)際學(xué)習(xí)過程沒有現(xiàn)在說的這么順利,但最終還是慢慢推導(dǎo)出了這樣一個(gè)流程圖:

          這個(gè)流程圖非常重要,它基本上讓我理透 Vetur 的兩個(gè)重要階段:

          • 啟動(dòng)階段,vls 類型會(huì)初始化化 projectService 對象,之后再監(jiān)聽各類 LSP 事件
          • 執(zhí)行階段,LSP 事件觸發(fā)時(shí),vls 會(huì)將事件直接委托給 projectService 對象處理,而 projectService 會(huì)做兩件事情:
            • 針對 SFC 文件做 region 切割,解析出 templatescriptstyle 等區(qū)塊
            • 針對不同區(qū)塊,調(diào)用 modes/xxx 對象的 doComplete 函數(shù)處理

          基于這個(gè)流程圖,邏輯上可以推斷出:「所有 LSP 請求最終都會(huì)按照代碼的類型流轉(zhuǎn)到相應(yīng)的」 **modes** 「文件夾上」,例如:

          • 對于 template 的格式化請求,最終會(huì)流轉(zhuǎn)到 modes/template/index.ts 文件的 format 函數(shù)做處理
          • 對于 style 的格式化請求,則流轉(zhuǎn)到 modes/style/index.ts 文件的 format 函數(shù)
          • 同理可以推導(dǎo)出包括代碼補(bǔ)全、hover 提示、跳轉(zhuǎn)到定義、錯(cuò)誤診斷等等高級特性上

          這個(gè)發(fā)現(xiàn)讓整個(gè) Vetur 的代碼架構(gòu)變得很扁平,后續(xù)研究具體特性的時(shí)候可以跳過前面這一對 LSP 請求的處理、分割步驟,直接找到對應(yīng)的 modes/xxx/index.ts 代碼。

          那么,如何分析代碼的執(zhí)行流程呢?我個(gè)人總結(jié)的方法有兩個(gè):「靜態(tài)猜想」 + 「動(dòng)態(tài)驗(yàn)證」,放在下一節(jié)細(xì)講。

          局部深入

          經(jīng)歷前面一系列步驟,儲(chǔ)備了足夠的背景知識與框架認(rèn)知后,我們可以開始逐行分析源碼,了解每一行、每一個(gè)變量、每一個(gè)函數(shù)、每一個(gè)模塊的具體作用與實(shí)現(xiàn)了。接下來我會(huì)介紹兩種行之有效的方法論:

          • 「靜態(tài)猜想」:“讀”源碼,從面上理解代碼邏輯并作出猜想
          • 「動(dòng)態(tài)驗(yàn)證」:“運(yùn)行”源碼,借用 debug 工具逐行跟蹤代碼執(zhí)行過程,必要時(shí)可以改動(dòng)原有代碼,驗(yàn)證猜想

          這兩種方法并不涇渭分明,通常是一邊看代碼,一邊做推測,有疑點(diǎn)馬上運(yùn)行起來驗(yàn)證猜想是否正確,靈活配合使用效果更佳。

          靜態(tài)分析 —— 做猜想

          所謂的靜態(tài)分析,說白了就是逐行逐句分析代碼,研究每一個(gè)變量、每一個(gè)過程的作用,是一個(gè)特別吃基礎(chǔ)知識和信息檢索能力的苦力活。雖然每個(gè)框架的實(shí)現(xiàn)細(xì)節(jié)不一樣,但還是有一些普適的技巧可以討論一下:

          • 函數(shù)層面,關(guān)注輸入輸出及副作用:
            • 函數(shù)接受什么結(jié)構(gòu)的參數(shù),這些參數(shù)經(jīng)過函數(shù)內(nèi)部的每一條語句之后會(huì)發(fā)生什么變化,或者如何影響語句的執(zhí)行
            • 函數(shù)執(zhí)行完畢之后,會(huì)返回什么結(jié)構(gòu)的結(jié)果,這些結(jié)果下一步會(huì)被誰消費(fèi),影響誰的執(zhí)行邏輯
            • 特別的,有不少庫的函數(shù)實(shí)現(xiàn)有明顯的“副作用”,不是那么“純”,包括 Webpack、Vetur、Eslint 等 —— 這會(huì)急劇提升理解成本,所以閱讀的時(shí)候多留個(gè)心眼
          • 分支語句中,優(yōu)先關(guān)注主流程,分支流程很容易增加心智負(fù)擔(dān),到后面就不認(rèn)得誰是誰了
          • 對于循環(huán)語句,通常可以關(guān)注循環(huán)之前的狀態(tài)與之后的狀態(tài),通過這些變化推斷循環(huán)的作用
          • 對于變量與子函數(shù),根據(jù)命名推斷作用,通常不必過度細(xì)究
          • 跳過參數(shù)校驗(yàn)、錯(cuò)誤處理等分支邏輯,抓主流程!抓重點(diǎn)!
          • 謹(jǐn)記你要研究的切入點(diǎn),遇到特別復(fù)雜的子模塊,先大致理解功能,點(diǎn)到為止,記下這個(gè)硬骨頭回頭再作為一個(gè)新的切入點(diǎn)繼續(xù)研究
          • 學(xué)點(diǎn)常用的設(shè)計(jì)模式,工廠、裝飾器、代理等等,這些模式的使用率非常高

          結(jié)合這些技巧,在分析過程中讀者應(yīng)該還是會(huì)遇到很多推測和問題:這個(gè)函數(shù)是干什么的;這個(gè)語句太復(fù)雜了,看不懂;這個(gè)循環(huán)太多 side effect 了,捋不出重點(diǎn)。有問題是好事,證明你開始能看出端倪了,這個(gè)時(shí)候就需要將框架運(yùn)行起來,并且逐步、動(dòng)態(tài)地觀察代碼的流轉(zhuǎn),驗(yàn)證你的猜想或者問題。

          動(dòng)態(tài)分析 —— 驗(yàn)證猜想

          經(jīng)歷前面靜態(tài)閱讀代碼后,相信讀者已經(jīng)有一些對代碼邏輯的基本推斷與問題,接下來就需要運(yùn)行框架,在任何有疑問的地方添加斷點(diǎn),觀察執(zhí)行棧、參數(shù)變化、環(huán)境變化、邏輯分支語句,確定輸入?yún)?shù)是如何確定的,輸出結(jié)果又會(huì)被誰消費(fèi)。

          不過,要修改、運(yùn)行起一個(gè)開源框架可能并不容易,通常需要關(guān)注三個(gè)點(diǎn):

          • 如果框架已經(jīng)接入了一些工程化工具,需要弄清楚如何將源碼編譯為運(yùn)行產(chǎn)物,例如 Vetur 項(xiàng)目接入了 tsc + rollup,對應(yīng)的命令為 yarn watch/compile
          • 如何啟動(dòng)調(diào)試模式,例如 Vetur 場景下需要借用 VS Code 的 .vscode/launch.json 配置文件 + F5 命令啟動(dòng)調(diào)試;而對于前端框架如 Vue、React,通常打開瀏覽器的 DevTool 面板即可
          • 如何插入調(diào)試語句,前端或 Node 場景下通常添加 debugger; 語句即可

          如果一段代碼你運(yùn)行不起來,那么你大概率是無法掌握它的,所以我才會(huì)在前面“「了解背景知識」”一節(jié)特意強(qiáng)調(diào)需要了解項(xiàng)目的入口、啟動(dòng)、調(diào)試方法,而一旦掌握了調(diào)試技能,那這份代碼在你面前就相當(dāng)于脫掉了所有外裝,可以逐行、逐句觀察代碼邏輯的動(dòng)態(tài)流轉(zhuǎn)。

          及時(shí)總結(jié)

          好記性永遠(yuǎn)不如爛筆頭,前面花了這么多時(shí)間不斷探索、驗(yàn)證,如果沒有及時(shí)做筆記,那這大概率會(huì)是一場無用功;而如果沒有及時(shí)做總結(jié),那大概率無法將信息內(nèi)化為你腦子里面的知識!

          當(dāng)然了,也沒必要把這事看得太難,總結(jié)的目的是對已經(jīng)掌握的零碎信息做一次抽象,撇除部分細(xì)節(jié),進(jìn)而整合、梳理成體系化的知識,重點(diǎn)在于內(nèi)化成自己的知識而不是具體形式形態(tài)!所以不要完美注意,不要糾結(jié)把語句寫的更漂亮,把圖畫的更好看,在確保基本邏輯通順的前提下怎么快怎么來。

          我個(gè)人的習(xí)慣是針對一個(gè)主題會(huì)單開一個(gè)自己可見的飛書文檔,閱讀過程不斷記筆記 —— 都是一些沒有任何修飾的大白話,一旦自我感覺到達(dá)一個(gè)節(jié)點(diǎn)就馬上做一次總結(jié),將代碼流程歸類為 N 個(gè)步驟 —— 盡量控制在 10 個(gè)以內(nèi),每個(gè)步驟寫清楚邏輯和輸出,條件允許的話還會(huì)同步畫一些粗糙的流程圖、時(shí)序圖、狀態(tài)機(jī)等。最后,攢到一定量級后我還會(huì)更進(jìn)一步地,輸出對外的文章或 PPT,作為對自己學(xué)習(xí)成果的驗(yàn)收。

          下一個(gè)切入點(diǎn)

          人類所知的物質(zhì),或人類創(chuàng)造的產(chǎn)品中,沒有一樣是絕對簡單的,優(yōu)秀的開源項(xiàng)目通常是非常復(fù)雜的組合體,如果僅僅停留在“怎樣實(shí)現(xiàn)某個(gè)具體功能”,那遠(yuǎn)還沒有掌握精髓,你還需要繼續(xù)探索,分析它“怎樣實(shí)現(xiàn)、組合多個(gè)單體功能”——但不是各自為政,互不相干的“多個(gè)”,而是互相融合成有機(jī)整體的“多個(gè)”。

          所以在理解某個(gè)切面后,我們可以繼續(xù)沿用上面的分析步驟,循環(huán)敲定下一個(gè)切入點(diǎn),可以是你之前遺留下來的復(fù)雜問題,或者某個(gè)新的功能特性,沿著這些碎片順藤摸瓜,逐步梳理成一套比較體系化的認(rèn)知。

          放在 Vetur 語境下,當(dāng)時(shí)經(jīng)過第一遍分析之后我基本上就掌握了 Vetur 的架構(gòu)、核心流程及 「代碼補(bǔ)全」 的實(shí)現(xiàn)細(xì)節(jié),了解到“「所有 LSP 請求最終都會(huì)按照代碼的類型流轉(zhuǎn)到相應(yīng)的」 **modes** 「文件夾上」”這一基本規(guī)則。接下來我想繼續(xù)挖掘其它特性的實(shí)現(xiàn)原理,包括錯(cuò)誤診斷、跳轉(zhuǎn)定義、智能提示等,于是重新設(shè)定切入點(diǎn),重新跑一邊搜索、流程分析、局部深入,循環(huán)往復(fù)并最終總結(jié)出一系列知識點(diǎn),匯總組織成在線分享:《如何開發(fā)一款 VS Code 語言插件 —— 以 Vetur 為例》,人生第一次直播。

          最佳實(shí)踐

          再聊聊我個(gè)人比較認(rèn)可的最佳實(shí)踐吧:

          • 設(shè)定好具體、可衡量的目標(biāo),不要為了學(xué)習(xí)而學(xué)習(xí),如果有切實(shí)的強(qiáng)訴求,那就別由于彷徨,馬上去做
          • 磨刀不誤砍柴工,不要上來就對著源碼瘋狂輸出,一定要花點(diǎn)時(shí)間站在高層視角去看框架的背景和生態(tài)
          • 抓大放小,忽略哪些還不熟悉的概念、語句、工具、分支邏輯,你要認(rèn)識到復(fù)雜事物的學(xué)習(xí)模型往往螺旋上升,逐步深入的,不可能過一遍就能掌握所有細(xì)節(jié)和精髓,如果一開始就過度關(guān)注細(xì)節(jié),通常會(huì)讓整個(gè)學(xué)習(xí)周期拉到無限長。要弄清楚啥時(shí)候,什么情況下應(yīng)該忽略細(xì)節(jié),什么時(shí)候應(yīng)該抓住不放 —— 這與你的目標(biāo)和切入點(diǎn)有很大的關(guān)系
          • 隨時(shí)筆記:一旦有任何新發(fā)現(xiàn)、新問題,做好筆記,記錄下來,這些都會(huì)成為繼續(xù)探索的重要線索
          • 隨時(shí)總結(jié):
            • 筆記記錄當(dāng)下的、零碎的發(fā)現(xiàn),總結(jié)則將這些線索串聯(lián)形成知識點(diǎn)。
            • 總結(jié)過程你會(huì)發(fā)現(xiàn)更多認(rèn)知漏洞,提出更多問題,可以反過來繼續(xù)挖掘
            • 好記性不如爛筆頭,探索的結(jié)果落到紙面上才會(huì)真正成為你自己的東西,極端一點(diǎn)看,沒有形成輸出的學(xué)習(xí)過程往往會(huì)隨著時(shí)間的流逝,變成徒勞

          學(xué)習(xí)畢竟很個(gè)人的事情,上面提到的這些方法、技巧、原則對我個(gè)人特別受用但實(shí)際效果必然還是因人而異的,如果讀者執(zhí)行過程中感覺特別膈應(yīng)特別難受,那完全可以適當(dāng)微調(diào),慢慢找出更適合自己的方式方法。


          瀏覽 44
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费观看黄a一级视频 | 激情久久成人午夜视频 | 天天爱天天干天天爽 | 先锋影音亚洲无码av | 欧美一级黄色丝袜大片免费 |