vue源码学习--vue源码学习入门
本文為開始學習vue源碼的思路整理。在拿到vue項目源碼的之后看到那些項目中的文件夾,會很困惑,不知道每個文件夾內的世界,怎么變換,怎樣的魔力,最后產生了vue框架。學習源碼也無從學起。我解決了這些困惑之后,形成了這篇文章----vue源碼學習入門。文章會根據我自己的學習思路,理清vue項目的大體框架。文章先介紹vue項目中各個文件夾中內包含的功能,然后是從package.json文件開始我們的尋找vue構造函數之旅,最后再從vue構造函數返回入口,看看旅程中的各個節點都為vue加了什么“戲”。
一、Vue項目結構
在GitHub中下載vue項目的源碼后解壓我們會得到如下的目錄。
每個文件夾是做什么的,在項目中的CONTRIBUTING有介紹。我翻譯了一下,形成了以下腦圖:
?
我們源碼學習,學習的是vue框架內容的源碼,而項目中包含的許多構建vuejs的配置文件和輔助vue代碼編寫的功能模塊是我們幾乎接觸不到的。
我們需要著重注意的是,包含vue源碼的【src】文件夾。我把src的內容單獨整理,形成了又一腦圖,如下:
?
現在我們大致知道vue項目的各個文件夾的用處了。而且,對于我們需要著重關注的源碼src也大致掌握了每個文件夾負責的功能模塊。接下來,我們就要開始啃食vue源碼了。做一只饑渴的寄生蟲吧~
?
二、尋找構造函數之旅
要了解一個前端項目,一般情況下我們都可以從查看項目的package.json文件開始。從package.json我們可以知道項目有哪些依賴包,定義了哪些腳本字段。其實在CONTRIBUTING中還提到了常用的npm命令。以下是我的截圖:
?
其中提及可以用npm run dev來達到監控和自動重建dist目錄下的vue.js文件以及還有一些npm命令在package.json的script中,大家可以按需執行。
那我們找到vue項目中的package.json,看看npm run dev執行了什么命令
rollup指的是一個專注于es6模塊構建的工具,會對其構建的代碼進行tree-shaking,是tree-shaking的提出者。它與webpack的區別是rollup沒做uplify,其構建后的代碼盡量保持源代碼的樣子。
rollup -c指定配置文件為build/config.js,最后將TARGET的值設置成web-full-dev。
打開build文件夾下的config.js。簡化后代碼如下:
const builds = {...// Runtime+compiler development build (Browser)'web-full-dev': {entry: resolve('web/entry-runtime-with-compiler.js'),dest: resolve('dist/vue.js'),format: 'umd',env: 'development',alias: { he: './entity-decoder' },banner}... }function genConfig (name) {const opts = builds[name]const config = {input: opts.entry,external: opts.external,plugins: [replace({__WEEX__: !!opts.weex,__WEEX_VERSION__: weexVersion,__VERSION__: version}),flow(),buble(),alias(Object.assign({}, aliases, opts.alias))].concat(opts.plugins || []),output: {file: opts.dest,format: opts.format,banner: opts.banner,name: opts.moduleName || 'Vue'}}if (opts.env) {config.plugins.push(replace({'process.env.NODE_ENV': JSON.stringify(opts.env)}))}Object.defineProperty(config, '_name', {enumerable: false,value: name})return config }if (process.env.TARGET) {module.exports = genConfig(process.env.TARGET) } else {exports.getBuild = genConfigexports.getAllBuilds = () => Object.keys(builds).map(genConfig) }?
我們可以看出,該js中包含著一個builds常量對象,對象的屬性名為之前package.json中npm對應命里里設置的TAGET值。js的最后根據是否有TAGET值返回不同的值。我們npm run dev的命令對應的是:
module.exports = genConfig({entry: resolve('web/entry-runtime-with-compiler.js'),dest: resolve('dist/vue.js'),format: 'umd',env: 'development',alias: { he: './entity-decoder' },banner});并最終返回一個config對象,也就是執行rollup的配置對象。
從builds對象中可以知道,vue的入口文件為:web/entry-runtime-with-compiler.js。在項目結構的介紹中,我們也提及了dist、build的入口文件在platform中。在經過resolve函數之后,路徑就是:\vue-dev\src\platforms\web\entry-runtime-with-compiler.js。
?
打開這個文件,在import中看到了Vue又來自./runtime/index即\vue-dev\src\platforms\web\runtime\index.js。按照這個Vue是來自于import的套路我們接著又找到了core/index.js、instance/index.js。撥開層層迷霧,終識廬山真面目,最終找到了最終的vue構造函數,如下:(\vue-dev\src\core\instance\index.js)
1 function Vue (options) { 2 if (process.env.NODE_ENV !== 'production' && 3 !(this instanceof Vue) 4 ) { 5 warn('Vue is a constructor and should be called with the `new` keyword') 6 } 7 this._init(options) 8 }所以尋找Vue構造函數之旅如下圖:
?
三、回首來路,旅程給Vue加了哪些戲
如果直接從Vue實例化開始閱讀源碼(一般思路)在閱讀的過程中常常會遇見一些沒有見過的屬性和方法。心中就會產生疑惑,這些屬性的值是啥,在哪賦值定義的。為了解決這個疑問,本節通過“回顧旅程”,其實也是vuejs執行順序,來看看程序對Vue構造函數都加了哪些“戲”。
Vue(options)作為函數本質上也是一個對象。作為構造函數其原型對象將會對實例產生影響。所以整理出了vuejs執行時掛載在函數上和函數原型上的屬性方法有哪些。
vue的構造函數的文件在\vue-dev\src\core\instance\index.js,源碼如下:
function Vue (options) {if (process.env.NODE_ENV !== 'production' &&!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options) }initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)export default Vue?
vue構造函數先是做一個安全模式的處理,告訴使用者必須通過new字符串調用Vue方法,然后用了一個_init(options)方法。可以推斷出_init方法是在Vue原型對象上的方法。
構造函數之外還執行了許多從幾個依賴中引入的函數,并將構造函數作為參數傳入方法。整理過后再instance/index.js中對Vue以及Vue原型的改變如下:
?
往上一層core/index.js中,這個js導入了已經在原型上掛載了方法合屬性后的Vue,然后導入initGlobalAPI為Vue導入許多全局API(我們在閱讀vue官方文檔中API分為全局配置、全局API以及實例屬性等全局API正是這時導入的)。initGlobalAPI中最后調用依賴引入的initUse、initMixin、initExtend、initAssetRegisters等方法
然后和在Vue原型對象上定義$isServer、$ssrContext兩個訪問器屬性和為Vue添加version屬性。具體添加見下圖:
?
?
再往回看,我們回到runtime/index.js。這個js顯示為Vue下的config屬性添加屬性和賦值,再是將平臺相關的directives和components擴展給Vue.options下的directives和components中也就是安裝平臺特有的指令和組件。
然后為判斷是否在瀏覽器中,如果是就為Vue原型對象安裝平臺patch功能。接著定義公共的$mount方法。
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean ): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating) }$mount方法先是根據是否在瀏覽器決定是否query(el),最后都將參數傳給core/instance/lifecycle.js中的mountComponent方法,該方法負責掛載更新組件。
具體添加如下:
?
最后是runtime/entry-runtime-with-compiler.js,這個文件首先是緩存原型對象中存在的$mount方法,然后用一個可以將template/el轉化為render函數的方法覆蓋原型對象的$mount方法。然后定義了Vue.compile屬性為compileToFunctions 函數。這個函數的作用就是將模板 template 編譯為render函數。如下圖所示:
匯總一下:
第一步:instance/index.js 為Vue.prototype定義實例屬性、實例方法/數據、實例方法/事件以及生命周期等實例相關的屬性方法。
第二步:core/index.js? 主要是通過initGlobalAPI掛載平臺相關的全局API 和Vue需要用到的工具函數
第三部:runtime/index.js? ?是添加平臺相關的指令、組件和配置參數和$mount方法
第四部? ? runtime/entry-runtime-with-compiler.js 給Vue添加了可以支持template的$mount方法和compiler編譯器。
回首之旅結束,希望你得到你想得到的。
如此一路走來,我想我們應該是知道vue源碼學習的大致框架了。知道了去哪找對應功能模塊的代碼,以及vue對象上的屬性和vue原型對象上的屬性有粗略的了解。如果遇見也知道要去哪認識他們,而不是帶著苦惱前行了。
希望能幫到你~
?注:本文章中學習的源碼版本為vue? 2.5.2. 文章中涉及的觀點和理解均是個人的理解,如有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~
轉載于:https://www.cnblogs.com/mlFronter/p/7730042.html
總結
以上是生活随笔為你收集整理的vue源码学习--vue源码学习入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 团队-游戏《石头,剪刀,布》-团队一阶段
- 下一篇: 学习vue第一篇记录