本文爲開始學習vue源碼的思路整理。在拿到vue項目源碼的以後看到那些項目中的文件夾,會很困惑,不知道每一個文件夾內的世界,怎麼變換,怎樣的魔力,最後產生了vue框架。學習源碼也無從學起。我解決了這些困惑以後,造成了這篇文章----vue源碼學習入門。文章會根據我本身的學習思路,理清vue項目的大致框架。文章先介紹vue項目中各個文件夾中內包含的功能,而後是從package.json文件開始咱們的尋找vue構造函數之旅,最後再從vue構造函數返回入口,看看旅程中的各個節點都爲vue加了什麼「戲」。前端
1、Vue項目結構vue
在GitHub中下載vue項目的源碼後解壓咱們會獲得以下的目錄。webpack
每一個文件夾是作什麼的,在項目中的CONTRIBUTING有介紹。我翻譯了一下,造成了如下腦圖:git
咱們源碼學習,學習的是vue框架內容的源碼,而項目中包含的許多構建vuejs的配置文件和輔助vue代碼編寫的功能模塊是咱們幾乎接觸不到的。es6
咱們須要着重注意的是,包含vue源碼的【src】文件夾。我把src的內容單獨整理,造成了又一腦圖,以下:github
如今咱們大體知道vue項目的各個文件夾的用處了。並且,對於咱們須要着重關注的源碼src也大體掌握了每一個文件夾負責的功能模塊。接下來,咱們就要開始啃食vue源碼了。作一隻飢渴的寄生蟲吧~web
2、尋找構造函數之旅npm
要了解一個前端項目,通常狀況下咱們均可以從查看項目的package.json文件開始。從package.json咱們能夠知道項目有哪些依賴包,定義了哪些腳本字段。其實在CONTRIBUTING中還提到了經常使用的npm命令。如下是個人截圖:json
其中說起能夠用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 = genConfig exports.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構造函數之旅以下圖:
3、回首來路,旅程給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) : undefined return 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. 文章中涉及的觀點和理解均是我的的理解,若有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~