小姐姐笔记:我是如何学习简单源码拓展视野的
大家好,我是若川。這是我上周組織的源碼共讀紀(jì)年小姐姐的筆記,寫得很好。所以分享給大家。歡迎加我微信 ruochuan12,進(jìn)源碼共讀群。其他更多人的筆記可以閱讀原文查看。
川哥的源碼解讀文章:據(jù)說(shuō) 99% 的人不知道 vue-devtools 還能直接打開對(duì)應(yīng)組件文件?本文原理揭秘
???? 非常感謝川哥組織的源碼閱讀活動(dòng)
1. 解讀前的準(zhǔn)備
1.1 粗略閱讀一遍川哥的源碼解讀文章,弄清楚文章的主旨內(nèi)容:探究 vue-devtools「在編輯器中打開組件」功能實(shí)現(xiàn)原理**,它的核心實(shí)現(xiàn)就是 launch-editor**。
1.2 明確自己到底要學(xué)習(xí)什么:
1)學(xué)習(xí)調(diào)試源碼的方法;
2)在調(diào)試過(guò)程中探究 launch-editor 源碼是如何實(shí)現(xiàn)在編輯器打開對(duì)應(yīng)的文件;
目標(biāo):跟著川哥的文章完整走完一遍調(diào)試的流程,并對(duì)外輸出記錄文檔。
1.3 資源:
下載川哥的源碼:git clone https://github.com/lxchuan12/open-in-editor.git,進(jìn)入 vue3-project 目錄,安裝依賴yarn install
安裝 vue-devtools 谷歌擴(kuò)展:翻墻去應(yīng)用商店下載安裝即可(下載 6.0.0 beta 版)
了解 launch-editor[1]:主要功能是在編輯器中打開帶有行號(hào)的文件
2. 開始學(xué)習(xí),淺嘗輒止
上述的準(zhǔn)備工作搞完之后,我們動(dòng)手操作一下。
2.1 開始動(dòng)手
我使用的編輯器是 VSCode。
打開 vue3-project 目錄的 package.json,點(diǎn)擊調(diào)試,選擇 serve。這一步操作,使得我們以 debug 的形式,運(yùn)行了 vue-cli-service serve 這個(gè)命令。
img跟著文章實(shí)現(xiàn)到這里的時(shí)候,我有點(diǎn)懵逼,因?yàn)槲也恢澜酉聛?lái)為什么突然要搜索【launch-editor-middleware】這個(gè)庫(kù)。
直到我再次通讀一遍文章,發(fā)現(xiàn)川哥前面有提到 vue-devtools 的 Open component in editor[2] 這個(gè)文檔,這個(gè)文檔里面描述了引用了【launch-editor-middleware】這個(gè)庫(kù)來(lái)實(shí)現(xiàn)打開文檔的功能。而我之前先入為主地以為,這期是解讀 vue-devtools 的源碼,其實(shí)這只是解讀實(shí)現(xiàn)打開文檔功能的源碼而已。
理解了這一層,我們可以直接搜項(xiàng)目里(包括 node_modules)里的【launch-editor-middleware】關(guān)鍵字,就可以找到這個(gè)庫(kù)的源碼位置了。
2.2 調(diào)試之旅
調(diào)試的流程就是打斷點(diǎn),點(diǎn)擊調(diào)試的流程面板,經(jīng)過(guò)不斷調(diào)試,觀察數(shù)據(jù)的變化。
下圖【launch-editor-middleware】的源碼,在這份源碼中我們能很輕易地分析出,最終運(yùn)行的是 launch 函數(shù),我們可以這這里打一個(gè)斷點(diǎn),然后進(jìn)入到【launch-editor】的源碼,實(shí)際運(yùn)行的是 launchEditor 函數(shù)。
imgimg粗略看一遍 launchEditor 函數(shù),發(fā)現(xiàn)它實(shí)際上是做了四件事:
獲取 fileName,lineNumber,columnNumber
異常處理:是否存在文件,onErrorCallback,是否存在 editor
猜測(cè)當(dāng)前正在使用的編輯器:guessEditor
使用 child_process.spwan 異步打開一個(gè)子進(jìn)程模塊,它調(diào)起了 cmd.exe 工具打開我們的編輯器,并打開了文件(args 就是文件的參數(shù))
看完了這個(gè)函數(shù),其實(shí)大概實(shí)現(xiàn)的原理也就出來(lái)了,核心代碼如下:
if?(process.platform?===?'win32')?{_childProcess?=?childProcess.spawn('cmd.exe',['/C',?editor].concat(args),{?stdio:?'inherit'?}) }?else?{_childProcess?=?childProcess.spawn(editor,?args,?{?stdio:?'inherit'?}) }但我們肯定還有很多疑惑,比如:
在瀏覽器控制臺(tái)點(diǎn)擊按鈕,編輯器是怎么接收到它的請(qǐng)求信息呢?
用到了哪些 API/編程技巧?
這個(gè)功能實(shí)現(xiàn)如果讓我們來(lái)實(shí)現(xiàn),是怎么實(shí)現(xiàn)呢(復(fù)述思路)?
3. 動(dòng)手操作,深入實(shí)踐
在前面的拆解中,雖然很多地方看似看懂了,但又沒(méi)完全懂,那我們來(lái)解答一下在看源碼的時(shí)候的疑問(wèn):
3.1 編輯器如何接收到瀏覽器的請(qǐng)求信息
點(diǎn)擊 vue-devtools 的按鈕時(shí),我們會(huì)發(fā)現(xiàn)它發(fā)送了一個(gè)請(qǐng)求:http://localhost:8080/__open-in-editor?file=src/components/HelloWorld.vue
img那編輯器是如何接收到這個(gè)請(qǐng)求呢?搜索【launch-editor-middleware】關(guān)鍵字,我們會(huì)發(fā)現(xiàn),在 @vue/cli-service 的 serve.js 文件中,使用了 app.use("/__open-in-editor"),用過(guò) express 的小伙伴會(huì)比較熟悉,這是express 引入中間件的用法。當(dāng)瀏覽器發(fā)送 http://localhost:8080/__open-in-editor?file=src/components/HelloWorld.vue 這個(gè)請(qǐng)求的時(shí)候,就進(jìn)入到下面這個(gè)代碼了。
//?vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js //?46行 const?launchEditorMiddleware?=?require('launch-editor-middleware') //?192行 before?(app,?server)?{//?launch?editor?support.//?this?works?with?vue-devtools?&?@vue/cli-overlayapp.use('/__open-in-editor',?launchEditorMiddleware(()?=>?console.log(`To?specify?an?editor,?specify?the?EDITOR?env?variable?or?`?+`add?"editor"?field?to?your?Vue?project?config.\n`)))//?省略若干代碼... }3.2 用到了哪些 API/編程技巧
3.2.1 函數(shù)的重載
在【launch-editor-middleware】的入口函數(shù)這里,使用了函數(shù)重載的寫法,這種寫法在很多源碼中都很常見,目的是方便用戶調(diào)用時(shí)傳參,針對(duì)不定量的參數(shù)對(duì)應(yīng)不同的操作內(nèi)容。
//?vue3-project/node_modules/launch-editor-middleware/index.js const?url?=?require('url') const?path?=?require('path') const?launch?=?require('launch-editor')module.exports?=?(specifiedEditor,?srcRoot,?onErrorCallback)?=>?{//?specifiedEditor?=>?這里傳遞過(guò)來(lái)的則是?()?=>?console.log()?函數(shù)//?所以和?onErrorCallback?切換下,把它賦值給錯(cuò)誤回調(diào)函數(shù)if?(typeof?specifiedEditor?===?'function')?{onErrorCallback?=?specifiedEditorspecifiedEditor?=?undefined}//?如果第二個(gè)參數(shù)是函數(shù),同樣把它賦值給錯(cuò)誤回調(diào)函數(shù)//?這里傳遞過(guò)來(lái)的是undefinedif?(typeof?srcRoot?===?'function')?{onErrorCallback?=?srcRootsrcRoot?=?undefined}//?srcRoot?是傳遞過(guò)來(lái)的參數(shù),或者當(dāng)前?node?進(jìn)程的目錄srcRoot?=?srcRoot?||?process.cwd()//?最后返回一個(gè)函數(shù),?express?中間件return?function?launchEditorMiddleware?(req,?res,?next)?{//?省略?...} }3.2.2 裝飾器模式
這段代碼 wrapErrorCallback 先執(zhí)行其他代碼,再去執(zhí)行 onErrorCallback,這種包裹函數(shù)的形式在很多源碼里都也很常見,可以理解為一個(gè)裝飾器,把 onErrorCallback 包裝了起來(lái),對(duì)原函數(shù)進(jìn)行了增強(qiáng)。
這也是設(shè)計(jì)模式中的裝飾器設(shè)計(jì)模式:
function?wrapErrorCallback?(cb)?{return?(fileName,?errorMessage)?=>?{console.log()console.log(chalk.red('Could?not?open?'?+?path.basename(fileName)?+?'?in?the?editor.'))if?(errorMessage)?{if?(errorMessage[errorMessage.length?-?1]?!==?'.')?{errorMessage?+=?'.'}console.log(chalk.red('The?editor?process?exited?with?an?error:?'?+?errorMessage))}console.log()if?(cb)?cb(fileName,?errorMessage)} }onErrorCallback?=?wrapErrorCallback(onErrorCallback)3.2.3 apply
apply 語(yǔ)法:func.apply(thisArg, [argsArray]),也經(jīng)常在源碼中可以看到。這里使用 apply 是把 extraArgs 作為 push 方法的 arguments 傳進(jìn)去。
if?(lineNumber)?{//?getArgumentsForPosition?返回一個(gè)數(shù)組const?extraArgs?=?getArgumentsForPosition(editor,?fileName,?lineNumber,?columnNumber)//?將?extraArgs?參數(shù)?push?到?args?里args.push.apply(args,?extraArgs) }?else?{args.push(fileName) }3.2.4 child_process
child_process 是 Node.js 的一個(gè)模塊,它提供了衍生子進(jìn)程的能力,默認(rèn)情況下,會(huì)在父 Node.js 進(jìn)程和衍生的子進(jìn)程之間建立 stdin、stdout 和 stderr 的管道。
3.2.5 process.platform
用于標(biāo)識(shí)運(yùn)行 Node.js 進(jìn)程的操作系統(tǒng)平臺(tái),返回字符串,目前可能的值有:?"aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32"
3.3 如何實(shí)現(xiàn)(復(fù)述思路)
瀏覽器與編輯器的通訊:借助 Node.js 進(jìn)程,與瀏覽器發(fā)生通訊
瀏覽器將需要打開的文件路徑通過(guò)參數(shù)傳遞給編輯器
判斷操作系統(tǒng)平臺(tái)和所使用的編輯器(每個(gè)平臺(tái)的命令行程序不一樣,每個(gè)編輯器的環(huán)境變量也不一樣)
借助 Node 調(diào)起 cmd.exe 工具打開我們的編輯器,打開對(duì)應(yīng)路徑的文件
4. 感想
編碼能力:通過(guò)解讀 launch-editor 源碼,學(xué)習(xí)/重溫了【函數(shù)的重載】【裝飾器模式】【apply 使用方法】,源碼的組織結(jié)構(gòu)也非常值得我們學(xué)習(xí),比如里面很多功能代碼都單獨(dú)封裝起來(lái),封裝成函數(shù)或者模塊,使得整個(gè)源碼的結(jié)構(gòu)非常清晰,核心通俗易懂,易于解讀和維護(hù)。(這也可以理解為自頂向下的編程方法)
拓展視野:源碼中包含了很多與 Node.js 相關(guān)的方法,有很多都是我不熟悉的,在解讀源碼的過(guò)程也是我學(xué)習(xí) Node.js 的過(guò)程。
工作中可能會(huì)用到:
開發(fā) VSCode 插件與外界通訊可借助 Node.js 進(jìn)程
裝飾器模式的應(yīng)用
判斷操作系統(tǒng)平臺(tái)
參考資料
[1]
launch-editor: https://github.com/yyx990803/launch-editor
[2]Open component in editor: https://github.com/vuejs/devtools/blob/legacy/docs/open-in-editor.md
最近組建了一個(gè)江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進(jìn)群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進(jìn)面試群)
我讀源碼的經(jīng)歷
面對(duì) this 指向丟失,尤雨溪在 Vuex 源碼中是怎么處理的
老姚淺談:怎么學(xué)JavaScript?
·················?若川簡(jiǎn)介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開發(fā)“工程師”。寫有《學(xué)習(xí)源碼整體架構(gòu)系列》多篇,在知乎、掘金收獲超百萬(wàn)閱讀。
從2014年起,每年都會(huì)寫一篇年度總結(jié),已經(jīng)寫了7篇,點(diǎn)擊查看年度總結(jié)。
同時(shí),活躍在知乎@若川,掘金@若川。致力于分享前端開發(fā)經(jīng)驗(yàn),愿景:幫助5年內(nèi)前端人走向前列。
識(shí)別上方二維碼加我微信、拉你進(jìn)源碼共讀群
今日話題
略。歡迎分享、收藏、點(diǎn)贊、在看我的公眾號(hào)文章~
總結(jié)
以上是生活随笔為你收集整理的小姐姐笔记:我是如何学习简单源码拓展视野的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 前端学习(3085):vue+eleme
- 下一篇: 基于Ubuntu的linux环境制作嵌入