1. 程式人生 > 實用技巧 >vue 原始碼(一)

vue 原始碼(一)

1. vue 目錄結構 (src)

|-- dist  打包後的vue版本
|-- flow  型別檢測,3.0換了typeScript    // 使用flow定義和檢測型別,增強程式碼健壯性
|-- script  構建不同版本vue的相關配置
|-- src  原始碼
    |-- compiler  編譯器    // 將template編譯成render函式
    |-- core  不區分平臺的核心程式碼
        |-- components  通用的抽象元件
        |-- global-api  全域性API
        |-- instance  例項的建構函式和原型方法
        |-- observer  資料響應式   // vue檢測資料變化並更新檢視的實現
        |-- util  常用的工具方法
        |-- vdom  虛擬dom相關  // 將render函式轉為vnode從而patch為真實dom以及diff演算法的程式碼實現
    |-- platforms  不同平臺不同實現
    |-- server  服務端渲染
    |-- sfc  .vue單檔案元件解析
    |-- shared  全域性通用工具方法
|-- test 測試

和執行時版本的關鍵所在,完整版就自帶這個編譯器,而執行時版本就沒有,
(面試題) 請問 runtime compiler 和 runtime-only 這兩個版本的區別?
檔案大小的區別,帶編譯的版本要比不帶的版本檔案要大6kb左右
編譯時機不同:編譯執行時版本會在執行時進行編譯,效能會有一定損耗;執行時版本藉助於 vue-loader 做的離線編譯,執行速度更快。

2.vue入口 首先是建構函式 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):定義_init方法。
stateMixin(Vue):定義資料相關的方法$set,$delete,$watch方法。
eventsMixin(Vue):定義事件相關的方法$on,$once,$off,$emit。
lifecycleMixin(Vue):定義_update,及生命週期相關的$forceUpdate和$destroy。
renderMixin(Vue):定義$nextTick,_render將render函式轉為vnode。

這些初始化後還會定義一些全域性的API,initGlobalAPI(),裡面包括

 initUse(Vue)   // Vue.use()
 initMixin(Vue)  // Vue.mixins()
 initExtend(Vue)  // Vue.extends()
 initAssetRegisters(Vue)  // Vue.component,Vue.directive,Vue.filter方法

this._init(options) 是 core/instance/init.js中的掛載到vue的proptype原型鏈上的。
此處的options是傳進來的Vue例項,eg:

new Vue(
  {
    el: '#app',
    data: {
    message: 'hello world'
  },
    components: {}
  }
)
Vue.prototype._init = function (options?: Object) {
// 如果options裡沒有傳components就開始解析構造器,傳了就初始化這個元件
if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        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')

所以在beforeCreate生命週期前vue 初始化了生命週期、事件處理機制
在created之前vue 初始化事件通訊、options相關的
vue/src/core/instance/index.js stateMixin(Vue)
state.js

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

這是初始化data函式的方法,其中將props和methods中的物件和函式與data中的比較,避免重複。這裡只在data裡傳了一個message欄位,走到until/lang.js isReserved()

export function isReserved (str: string): boolean {
  const c = (str + '').charCodeAt(0)
  return c === 0x24 || c === 0x5F
}

0x24和0x5F是$和_對應的ASCII碼值,isReserved函式是判斷data裡的欄位是否帶有$和_,為true會走到 proxy(vm, `_data`, key)這裡,這裡使用了代理模式

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

這裡是Object.defineProperty的實現,set和get, 然後實現observe(data, true /* asRootData */)
../observer/index.js

xport function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}