1. 程式人生 > >vue原始碼之入口檔案解析

vue原始碼之入口檔案解析

由於專案中經常使用vue,所以這次趁有機會趕緊拜讀下原始碼,體驗下vue原始碼的設計風采。


一、下載原始碼

二、原始碼專案目錄

三、載入core核心入口檔案index.js


通過看原始碼我們可以得知這個入口檔案:

(1)引入了幾個物件,包括Vue構造方法
(2)初始化全域性API : initGlobalAPI(Vue)
(3)三個屬性攔截器,其中兩個攔截監聽Vue.prototype,另一個攔截監聽Vue
(4)定義vue的版本
(5)匯出Vue構造方法

四、載入Vue定義的檔案index.js


1.通過觀察原始碼,我們可以發現:
(1)引入了一些需要Vue這個構造方法的方法
(2)Vue構造方法中呼叫初始化方法 this._init(options)
(3)將Vue轉為引數,向引入的函式中傳遞
(4)將Vue預設匯出

2.解析核心函式:

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)
}

(1)這個構造方法決定了呼叫的方式

let vm = new Vue({ //選項 });

(2)if 判斷是否用new關鍵字例項化Vue,否則報錯
(3)初始化方法this._init(options) ,將options物件(data,props,methods,filters … )當引數傳遞
(4)呼叫方法:

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

注:所以_init(obj)方法則是在這些方法中定義

五、解析初始化檔案instancce/init.js

1.init.js檔案中有一個很重要的方法initMixin,這個方法的作用是將vm所需的各種初始化變數與函式混入到vm物件中
2.函式的開頭主要定義了一些屬性和標記,然後合併選項,合併選項的條件如下:

if (options && options._isComponent) {
  initInternalComponent(vm, options)
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}


(1)且先看if條件,當options存在並且options._isComponent為true時,就會呼叫initInternalComponent(vm, options)
(2)options._isComponent 用來記錄當前options是否屬於元件。而initInternalComponent()從函式名看,其作用應當是初始化內部元件。
(3)如果,當options屬於一個元件時,就對內部元件(internal component)進行初始化。
(4) 否則,將傳入的options與vm本身的屬性進行了合併,並重新賦值給vm.$options。
(5)結果,Vue例項會將傳入的使用者自定義options合併到自身屬性中。

3. 然後初始化代理

/* 初始化代理 */
if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}

4. 把自身的例項給暴露出來,傳遞給其他方法,為Vue的例項新增各種介面,事件,以及可呼叫的屬性

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')

解析:callHook(vm, 'beforeCreate')、callHook(vm, 'created')

說明在beforeCreate生命週期前需要執行:initLifecycle(),initEvents (),initRender();而在created生命週期前需要執行initInjections(),initState(),initProvide();


5. Dom掛載

/* 當el存在時,將其掛載到vm例項上 */
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}


六、初始化生命週期檔案instance/lifecycle.js

1.這個檔案主要定義了vue的生命週期相關的初始化方法
2.核心方法 iniflifecycle( ) 解析

let parent = options.parent
if (parent && !options.abstract) {
  while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent
  }
  parent.$children.push(vm)
}

vm.$parent = parent
vm.$root = parent ? parent.$root : vm

(1)在方法的開頭逐級查詢到以第一個非抽象的父級,如果 parent.$parent存在 且 parent.$options.abstract 為真,再次往上尋找
(2)從當前Vue例項vm開始向上查詢,找到最近的一級abstract為false的parent。將vm push給它的$children
(3)將vm的parent修改為上一步中找到的parent
(4)修改vm的根$root。若vm的parent存在,則與parent的$root統一;否則$root就是vm自身
(5)修改vm的各種變數
(6)結果:經過initLifecycle(),vm的parent,以及parent的children,都得到了更新。同時為vm新增了各種變數

七、其他混入方法

stateMixin(Vue) // 混入props,methods,data,computed,watch
eventsMixin(Vue) // 混入_events,並更新元件的listeners
lifecycleMixin(Vue) // 混入_vnode,$el,$parent相關
renderMixin(Vue) // 混入渲染相關的內容,如$slots等

經過這些翻方法的混入,Vue由一個空物件,變為擁有一堆變數與函式的龐大物件

八、全域性初始化src/core/golbal-api.js

1.在initGlobalAPI方法中的開頭,主要實現了一個configDef物件的config屬性的攔截監聽

// 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.'
    )
  }
}

/* 監聽config屬性值得變化 */
Object.defineProperty(Vue, 'config', configDef)

2.為Vue的util設定了幾個成員,包含 :warn,extend,mergeOptions,defineReactive 且暴露出去3.為Vue設定了set,delete,nextTick,以及一個空的options。其中options按型別填充預設空物件

Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
  Vue.options[type + 's'] = Object.create(null)
})

4.將 options.components 與 builtInComponents 合併

extend(Vue.options.components, builtInComponents)

5.初始化use,mixin,extend,assetRegisters全域性方法

initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)