Vue源碼解析(一): 建立vue程序的背後發生了什麼

主要大綱:vue

  • 從initGlobalAPI方法看Vue.config全局配置
  • 尋根問祖-Vue的構造函數的出生地

先來一段最多見的vue代碼demoweb

<div id="app">
  {{ message }}
</div>
// js
var vm = new Vue({
  el: '#app',
  data: {
    message: ‘hello vue'
  }
})

上面已經建立了一個vue應用程序;從上面很容易就看出來 Vue是一個構造器,vm是用這個構造器構造出來的實例化對象,實例化的時候傳入了參數,參數中包括el和data
上述延伸了3個問題:api

  1. Vue 構造器是什麼模樣?
  2. Vm可使用的方法,即vue的開放API都在源碼裏面怎麼實現的?
  3. 咱們傳入構造方法內的參數發生了什麼

這些問題是咱們解鎖vue源碼的最開始的步驟,因此咱們不妨經過vue源碼的入口開始尋找這些源碼的實現app


在源碼的src/platforms/web下面放着不一樣版本的構建entry文件,這些文件中導出export的Vue,都是從src/core/instance/index這個文件import過來的,
咱們先看下入口文件能帶給咱們什麼答案:dom

// src/core/instance/index
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)

這個入口文件作了3件事:ide

  1. 引用了 ./instance/index, 暴露了vue的來源,即構造器
  2. 調用initGlobalAPI方法,將vue傳進去, 給vue拓展了全局靜態方法
  3. 將vue暴露出去

這個入口文件的意義,是在暴露vue以前,給vue經過initGlobalAPI方法給vue拓展了全局靜態方法,對應Vue的外部API是 Vue.config,包含了Vue的全局配置,函數

Vue.config.silent // 日誌與警告
Vue.config.errorHandler // 這個處理函數被調用的時候,能夠獲取錯誤信息和Vue實例
Vue.config.devtools // 配置是否容許 vue-devtools 檢查代碼
…..

從initGlobalAPI方法看Vue.config全局配置

initGlobalAPI方法定義了configDef對象,它的getter方法會的屬性值是config,setter方法給出警告不容許修改。最後在vue上添加了config屬性,屬性描述返回configDef對象ui

export function initGlobalAPI (Vue: GlobalAPI) {
    // config
    const configDef = {}
    configDef.get = () => config
    if (process.env.NODE_ENV !== 'production') {
        configDef.set = () => {
            warn(
                    'Do not replace the Vue.config object, set individual fields instead.'
                )
      }
}
Object.defineProperty(Vue, 'config', configDef) // 添加config屬性

除此以外,還定義了util屬性,可是並無暴露到外面,也並不建議外部去使用this

尋根問祖-Vue的構造函數的出生地

瞭解了構造函數,也就知道了new vue()的時候發生了什麼
下面這段代碼就是Vue的構造方法,咱們能夠直觀的看出vue構造器是使用ES5的Function去實現類,是由於能夠經過prototype往vue原型上拓展不少方法,把這些方法拆分到不一樣的文件/模塊下,這樣更有利於代碼的維護,與協同開發
好比在這個文件中,能夠看到把Vue看成一個參數傳進下面的**Mixin方法中,這些方法都是經過接收vue,在它的prototype上面定義一些功能的;prototype

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’)
            //   vue必須是new vue()的實例化對象
           }
        console.log('options', options)

        this._init(options) // 調用內部_init方法
}
initMixin(Vue) // 在created生命週期函數以前的操做
stateMixin(Vue) // 利用 definedProperty 進行靜態數據的訂閱發佈
eventsMixin(Vue) // 實例事件流的注入, 利用的是訂閱發佈模式的事件流構造
lifecycleMixin(Vue) // 
renderMixin(Vue) // 實現 _render 渲染虛擬dom
export default Vue

這個構造函數的最核心點,就是this._init(options)

在此處打斷點,能夠看到參數options傳進來的就是外面咱們實例化時傳入的參數el 和 data

new Vue({
    el: '#app',
      data: {
        message: ‘hello vue'    
      }
})

這個_init方法出自initMixin 函數
看完這個函數,咱們梳理出整個初始化階段源碼的幾個重要的節點

  1. 初始化options參數進行合併配置
  2. 初始化生命週期
  3. 初始化時間系統
  4. 初始化state,包括data、props、 computed、watcher

export function initMixin (Vue: Class<Component>) {

console.log('Vue', Vue)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid 實例化的uid遞增1
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */

...

// 用_isVue來標識當前的實例是個Vue實例,這樣作是爲了後續被observed
vm._isVue = true    
// 合併配置options,並判斷是不是內部Component的options的初始化
if (options && options._isComponent) {
    // 內部
    initInternalComponent(vm, options)
} else {
    // 非內部
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
}
// 在render中將this指向vm._renderProxy
if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
} else {
    vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化生命週期
initLifecycle(vm)    
// 初始化事件註冊
initEvents(vm)
// 初始化渲染
initRender(vm)
// 觸發回掉函數中的beforeCreate鉤子函數
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
// 初始化vm的狀態,包括data、props、computed、watcher等
initState(vm)
initProvide(vm) // resolve provide after data/props
// vm已經建立好來,回掉created鉤子函數
callHook(vm, 'created’)
/* istanbul ignore if */
…
// 將實例進行掛載
if (vm.$options.el) {
    vm.$mount(vm.$options.el)
    }
}

}

相關文章
相關標籤/搜索