Vue源碼分析(一) : new Vue() 做了什么
author: @TiffanysBear
在了解new Vue做了什么之前,我們先對Vue源碼做一些基礎的了解,如果你已經對基礎的源碼目錄設計等有基礎的了解的話,可以跳過下面這部分。
源碼目錄設計
Vue.js 的源碼都在 src 目錄下,其目錄結構如下。
src
├── compiler # 編譯相關
├── core # 核心代碼
├── platforms # 不同平台的支持
├── server # 服務端渲染
├── sfc # .vue 文件解析
├── shared # 共享代碼
compiler
compiler 目錄包含 Vue.js 所有編譯相關的代碼。它包括把模板解析成 AST 語法樹,AST語法樹優化,代碼生成等功能。
編譯的工作可以在構建時做(可以借助 webpack、vue-loader 等插件);也可以在運行時做,使用包含構建功能的 Vue.js。編譯是一項耗性能的工作,所以更推薦前者——離線編譯。
core
core 目錄包含了 Vue.js 的核心代碼,包括有內置組件、全局 API 封裝,Vue 實例化、Obsever、Virtual DOM、工具函數 Util 等等。
platform
Vue.js 是一個跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客戶端上。platform 是 Vue.js 的入口,2 個目錄代表 2 個主要入口,分別打包成運行在 web 上和 weex 上的 Vue.js。
server
Vue.js 2.0 支持了服務端渲染,所有服務端渲染相關的邏輯都在這個目錄下。注意:這部分代碼是跑在服務端的 Node.js,不要和跑在瀏覽器端的 Vue.js 混為一談。
服務端渲染主要的工作是把組件渲染為服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最后將靜態標記"混合"為客戶端上完全交互的應用程序。
sfc
通常我們開發 Vue.js 都會借助 webpack 構建, 然后通過 .vue 單文件來編寫組件。
這個目錄下的代碼邏輯會把 .vue 文件內容解析成一個 JavaScript 的對象。
shared
Vue.js 會定義一些工具方法,這里定義的工具方法都是會被瀏覽器端的 Vue.js 和服務端的 Vue.js 所共享的。
接下來我們來找一下Vue的入口文件,我們接下來的分析都是基於platform為web的環境下進行的分析,從 package.json 和 config的 的打包配置中里可以看出,運行在web環境 (Runtime only (CommonJS))
的入口文件在 web/entry-runtime.js
下。
Vue入口文件
Vue入口文件目錄 vue/src/core/instance/index.js
// vue/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
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
采用的是ES5的寫法,並不是ES6的Class寫法的優點,是因為:
1、使用混入Mixin的方式傳入Vue,為Vue的原型prototype上增加方法。class難以實現這種方法
2、此種方式將代碼模塊合理划分,將擴展分散到多個模塊中去實現,使得代碼文件不會過於龐大,便於維護和管理。這個編程技巧以后可以用於代碼開發實現中。
通過Mixin增加的原型方法:
// vue/src/core/instance/index.js
initMixin(Vue) // _init
stateMixin(Vue) // $set、$delete、$watch
eventsMixin(Vue) // $on、$once、$off、$emit
lifecycleMixin(Vue) // _update、$forceUpdate、$destroy、
renderMixin(Vue) // $nextTick、_render
initGlobalAPI
在 vue/src/core/index.js 中,調用的initGlobalAPI(Vue),是為Vue增加靜態方法的,
在路徑 vue/src/core/global-api/ 目錄下的文件中,都是給Vue添加的靜態方法
比如:
Vue.use // 使用plugin
Vue.extend
Vue.mixin
Vue.component
Vue.directive
Vue.filter
有了這些基礎的了解和一步步的跟蹤查找后,我們一步一步找到了 new Vue
所在的位置,接下來我們來看下 new Vue
到底做了什么?
new Vue 做了什么
從入口的文件看來,通過new關鍵字初始化,調用了
// src/core/instance/index.js
this._init(options)
然后從Mixin增加的原型方法看,initMixin(Vue),調用的是為Vue增加的原型方法_init
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
....
....
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
....
....
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
所以,從上面的函數看來,new vue所做的事情,就像一個流程圖一樣展開了,分別是
- 合並配置
- 初始化生命周期
- 初始化事件中心
- 初始化渲染
- 調用
beforeCreate
鈎子函數 - init injections and reactivity(這個階段屬性都已注入綁定,而且被
$watch
變成reactivity,但是$el
還是沒有生成,也就是DOM沒有生成) - 初始化state狀態(初始化了data、props、computed、watcher)
- 調用created鈎子函數。
在初始化的最后,檢測到如果有 el 屬性,則調用 vm.$mount 方法掛載 vm,掛載的目標就是把模板渲染成最終的 DOM。
Vue代碼初始化的主線邏輯非常分明,使得邏輯和流程非常清楚,這種編程方法值得學習。
最后
現在的部分只是粗略的函數上講了 new Vue
的過程和含義,接下來的文檔會繼續對Vue的源碼進行學習和分析,會接着更細地分析在生命周期lifecyle下每一個函數后面具體所做的事情。