vue源碼學習--vue源碼學習入門

本文爲開始學習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. 文章中涉及的觀點和理解均是我的的理解,若有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~

相關文章
相關標籤/搜索