vue 源碼詳解(一):原型對象和全局 `API`的設(shè)計

vue 源碼詳解(一):原型對象和全局 API的設(shè)計

1. 從 new Vue() 開始

我們在實際的項目中使用 Vue 的時候 , 一般都是在 main.js 中通過 new Vue({el : '#app , ...options}) 生成根組件進行使用的撕彤, 相關(guān)的配置都通過 options 傳入。 Vue 的原型對象會幫我們初始化好很多屬性和方法梯捕, 我們可以通過 this.property 直接調(diào)用即可掰派; 而 Vue 這個類也通過類的靜態(tài)方法初始化了一些全局的 api维费, 我們可以通過類名直接調(diào)用怔毛, 比如 Vue.component() 员萍。 Vue 的原型對象和全局 API 是通過混入的方式融入 Vue 中的。

如下面代碼所示拣度, import Vue from './instance/index' 引入 Vue 的構(gòu)造函數(shù)碎绎,在用戶調(diào)用之前, Vue 先做了一些初始化工作抗果, 具體做了哪些工作看 <a href="#vue/src/core/instance/index.js">vue/src/core/instance/index.js(點擊跳轉(zhuǎn)) </a>中的代碼(下邊第二段):

  1. function Vue (options) { ... } 定義了 Vue 構(gòu)造函數(shù)筋帖, 我們調(diào)用 new Vue 時,只會執(zhí)行一句代碼冤馏, 即 this._init(options);
  2. 定義完構(gòu)造函數(shù)日麸,依次調(diào)用 initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue), 從而將 Vue 的初始化函數(shù)宿接、狀態(tài)初始化函數(shù)赘淮、事件初始化函數(shù)、生命周期初始化函數(shù)睦霎、渲染函數(shù)混入到 Vue 的原型對象。這才使得每個組件都有了便捷的功能走诞。初始化函數(shù)具體都做了什么工作副女, 且看后續(xù)的分析。

vue/src/core/index.js :

import Vue from './instance/index' // 1. 引入 Vue 構(gòu)造函數(shù)
import { initGlobalAPI } from './global-api/index' // 2. 引入初始化全局 API 的依賴
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue) // 3. 初始化全局 API

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

<strong id="vue/src/core/instance/index.js">vue/src/core/instance/index.js</strong>

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) // 1. Vue 實例初始化
}

initMixin(Vue) // 2
stateMixin(Vue) // 3
eventsMixin(Vue) // 4
lifecycleMixin(Vue) // 5
renderMixin(Vue) // 6

export default Vue

注釋 1 處蚣旱, new Vue() 時碑幅, 只執(zhí)行了一個初始化工作 this._init(options) ; 值得注意的是戴陡, 在定義完成構(gòu)造函數(shù)后,此時尚未有 new Vue 的調(diào)用沟涨, 即在實例創(chuàng)建之前恤批, 會執(zhí)行注釋 2 3 4 5 6 處的初始化工作, 讓后初始化全局 API ,至此準(zhǔn)備工作已經(jīng)就緒裹赴, 通過調(diào)用 new Vue 生成 Vue 實例時喜庞,會調(diào)用 this._init(options) 。接下來棋返,探索一下 Vue 生成實例前延都, 依次做了哪些工作。

1.1 initMixin (vue\src\core\instance\init.js)

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this // 1. vm 即 this睛竣, 即 Vue 的實例對象
    // a uid
    vm._uid = uid++ // 每個 Vue 實例對象都可以看成一個組件晰房, 每個組件有一個 _uid 屬性來標(biāo)記唯一性

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options 
    // 合并參數(shù), options 是我們調(diào)用 `new Vue({ el : 'app'chuand, ...args})` 時傳入的參數(shù)
    // 合并完成后將合并結(jié)果掛載到當(dāng)前 `Vue` 實例
    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( // 合并完成后將合并結(jié)果掛載到當(dāng)前 `Vue` 實例
        // 這個函數(shù)會檢查當(dāng)前 Vue 實例的否早函數(shù)和其父類、祖先類上的 options 選項, 并能監(jiān)聽是否發(fā)生了變化罕邀, 將 祖先類锉矢、父類和當(dāng)前 Vue 實例的 options 合并到一起
        resolveConstructorOptions(vm.constructor), 
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 1. 初始化聲明周期
    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')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

1.1.1 initLifecycle

上邊代碼給每個實例標(biāo)記一個唯一的 _uid 屬性, 然后標(biāo)記是否為 Vue 實例, 將用戶傳入的參數(shù)和 Vue 自有參數(shù)合并后吁津,掛載到 Vue$options 屬性 。

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  // 這個注釋已經(jīng)很明了了, 就是查找當(dāng)前 vue 實例的第一個非抽象父組件
  // 找到后會將當(dāng)前的組件合并到父組件的 `$children` 數(shù)組里
  // 從而建立了組件的父子關(guān)系
  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

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null // 這倆先忽略距误, 俺也不知道干嘛的
  vm._directInactive = false // 這倆先忽略, 俺也不知道干嘛的
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

如上扁位, 初始化聲明周期的時候准潭, 會簡歷當(dāng)前組件與其他組件的父子關(guān)系, 如果找到父組件域仇, 會將 $root 指針指向父組件刑然,找不到的話, 指向當(dāng)前 Vue 實例暇务。接下來 vm.$children = [] 初始化子組件列表泼掠, vm.$refs = {} 初始化引用列表, vm._watcher = null 初始化觀察者列表垦细, 此時還沒有觀察者择镇,無法檢測數(shù)據(jù)變化, vm._isMounted = false 標(biāo)記當(dāng)前組件尚未掛載到 DOM, vm._isDestroyed = false 標(biāo)記當(dāng)前組件并不是一個被銷毀的實例括改,這與垃圾回收有關(guān)系的腻豌, vm._isBeingDestroyed = false 標(biāo)記當(dāng)前組件是否正在銷毀工作。

至此, 聲明周期的初始化已經(jīng)完成了吝梅。

1.1.2 initEvents

vue/src/core/instance/events.js :

1.2 stateMixin : 狀態(tài)初始化

vue/src/core/instance/state.js :

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef) // 1
  Object.defineProperty(Vue.prototype, '$props', propsDef) // 2

  Vue.prototype.$set = set // 3
  Vue.prototype.$delete = del // 4

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

上邊代碼對實例的狀態(tài)做了初始化虱疏。 在注釋 1 2 兩個地方分別給 Vue 原型對象增加了 $data $props 兩個屬性, 這兩個屬性的值分別是當(dāng)前 vm_data _props 屬性苏携, 并且設(shè)置這兩個屬性是不可以修改的做瞪。

注釋 3 4 處為 vm 添加了 setdelete 方法, setdelete 是干嘛的就不用介紹了吧右冻, Vue 對象本身也有 Vue.setVue.delete 這兩個方法装蓬, 都是來源于下邊 set 這個函數(shù), 他的作用體現(xiàn)在下邊代碼注釋的 1 2 處:

參數(shù) target 為對象或者數(shù)組, target 有一個 __ob__ 屬性国旷, 這個屬性的來源是在 Observer 這個類中的構(gòu)造函數(shù)矛物,其中有一句是 def(value, '__ob__', this) , value 是待觀測的對象, 也就是我們寫代碼時傳入的 data中的屬性跪但, 然后我們傳入的 data 其實都被代理到 __ob__ 這個屬性上了履羞,以后我們操作 data 中的數(shù)據(jù)或者訪問 data 中的數(shù)據(jù)都會被代理到 __ob__ 這個屬性。

之后又在原型對象掛載了 $watcher 方法屡久, 該方法的返回值是一個銷毀 watcher 的方法忆首。 至于 watcher 是個啥, 以及 watcher 的作用被环,后邊再談糙及。

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val) // 1
  ob.dep.notify() // 2
  return val
}

vue\src\core\util\lang.js :

/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

1.3 事件初始化

其實就是在 Vue 原型對象上掛載了一些方法 ($on $once $off $emit) , 基于發(fā)布訂閱模式筛欢,實現(xiàn)了一個事件響應(yīng)系統(tǒng)浸锨, 與 nodejs 中的 eventEmitter 是極其相似的。這就是我們常用的事件總線機制的來源版姑。

簡單解析一下下面的代碼 :

$on 是事件的訂閱柱搜, 通過他的參數(shù) (event: string | Array<string>, fn: Function) 可知, 可以一次訂閱多個事件剥险,他們共享一個處理函數(shù)聪蘸, 然后將所有的處理函數(shù)以鍵值對的形式({eventName : handler[]})存儲在 vm._events 對象中,等待事件發(fā)布表制。一旦事件發(fā)布健爬, 就會根據(jù)事件類型( eventName )去事件處理函數(shù)列表(handler[])中,讀取處理函數(shù)并執(zhí)行么介。

$emit 是事件的發(fā)布娜遵, 生產(chǎn)環(huán)境中對事件名稱(也就是類型),進行了大小寫轉(zhuǎn)換壤短, 不用區(qū)分事件名稱的大小寫了魔熏, 當(dāng)然我們編碼不能這樣粗狂的去寫哈衷咽。 然后 cbs 是根據(jù)事件名稱讀取的處理函數(shù)的列表蒜绽, const args = toArray(arguments, 1) 是處理事件的參數(shù), 函數(shù) toArray$emit 函數(shù)的參數(shù)除掉第一個以后躲雅, 最終傳入了我們的訂閱函數(shù)中。 即
vm.$emit('render', 'a',124) 代碼最終調(diào)用結(jié)果是 vm._events['render'] 列表中所有的函數(shù)都以 ('a', 123) 為參數(shù)運行一次相赁。

$off 是將事件的訂閱函數(shù)從訂閱列表中刪除, 它提供了兩個參數(shù) (event?: string | Array<string>, fn?: Function), 兩個參數(shù)都是可選的钮科, 并且不能只穿第二參數(shù)。 如果實參列表為空婆赠, 則當(dāng)前 vm 上訂閱的所有事件和事件的處理函數(shù)都將被刪除;如果第二參數(shù)為空休里, 則當(dāng)前 vmvm._events[event] 中所有的處理函數(shù)將被清空; 如果第二個參數(shù) fn 不為空妙黍, 則只將 vm._events[event] 事件處理列表中的 fn 函數(shù)刪除悴侵。

$once 表示事件處理只執(zhí)行一次, 多次發(fā)布事件拭嫁,也只會執(zhí)行一次處理函數(shù)可免。這個函數(shù)有點小技巧。先建立一個 on 函數(shù)做粤, 然后把事件處理函數(shù) fn 掛載到這個函數(shù)對象上, 函數(shù)也是對象浇借,可以有自己的屬性,這個沒有疑問吧驮宴。 on 函數(shù)中只有兩句代碼 vm.$off(event, on), 讓 vm 解除 on 函數(shù)的訂閱逮刨, 這就可以保證以后不會再執(zhí)行 on 函數(shù)了; 下一句fn.apply(vm, arguments) 調(diào)用 fn , 這保證了 fn 被執(zhí)行了一次堵泽。 哈哈哈修己, 666.

事件的初始化就這樣講完了。

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

1.4 lifecycleMixin 生命周期初始化

代碼如下迎罗, 在 Vue 的原型對象上增加了三個方法 _update $forceUpdate $destroy, 依次來看下都做了什么事吧睬愤。

vm._update 通過 __patch__ 函數(shù)把虛擬節(jié)點 vnode 編譯成真實 DOM. 并且, 組件的更新也是在這里完成虛擬節(jié)點到真實 DOM 的轉(zhuǎn)換纹安。并且父組件更新后尤辱, 子組件也會更新砂豌。

vm.$forceUpdate 若果當(dāng)前組件上有觀察者, 則直接更細組件光督。

vm.$destroy 銷毀組件阳距, 如果當(dāng)前組件正在走銷毀的流程,則直接返回结借, 等待繼續(xù)銷毀筐摘。 否則, 會觸發(fā) beforeDestroy 這個聲明周期船老, 并將當(dāng)前組件標(biāo)記為正在銷毀的狀態(tài)咖熟。 然后將當(dāng)前組件從父組件中刪除, 然后銷毀所有的 watcher柳畔, 銷毀 vm._data__ob__ , 標(biāo)記組件狀態(tài)為 已銷毀,重新生成真實 DOM , 觸發(fā) destroyed 生命周期方法薪韩, 移除當(dāng)前組件訂閱的事件和事件的處理函數(shù), 將當(dāng)前組件對父組件的引用清空躬存。

vue/src/core/instance/lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

1.5 renderMixin 渲染函數(shù)初始化

也是向 Vue 的原型對象掛載一些方法宛逗。

installRenderHelpers(Vue.prototype) 向 vm 增加了模板的解析編譯所需要的一些方法;

$nextTick 即我們在寫代碼時常用的 this.$nextTick() , 它返回一個 Promise 實例 p盾剩, 我們可以在 pthen 函數(shù)中訪問到更新到 DOM 元素的數(shù)據(jù), 也可以向 this.nextTick 傳遞一個回調(diào)函數(shù) f告私, f 也可以訪問更新到 DOM 元素的數(shù)據(jù)。

_render 方法生成虛擬節(jié)點驻粟。詳見后邊的代碼。

vue/src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}

2. Vue 全局 API

Vue 中全局 API 一共有一下 12 個。全局 API 是通過構(gòu)造函數(shù) Vue 直接調(diào)用的酷麦, 有一些方法在實例上也做了同步, 可以通過實例對象去調(diào)用沃饶。 比如常用的 Vue.nextTick , 可以通過 this.$nextTick 進行調(diào)用轻黑。下面就依次分析一下每個全局 API 的使用和實現(xiàn)思路吧氓鄙。

  • Vue.extend
  • Vue.nextTick
  • Vue.set
  • Vue.delete
  • Vue.directive
  • Vue.filter
  • Vue.component
  • Vue.use
  • Vue.mixin
  • Vue.compile
  • Vue.observable
  • Vue.version

src/core/global-api/index.js

2.1 Vue.extend

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椎咧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子勤讽,更是在濱河造成了極大的恐慌脚牍,老刑警劉巖巢墅,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驯遇,居然都是意外死亡,警方通過查閱死者的電腦和手機叉庐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門会喝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肢执,你說我怎么就攤上這事⌒肆铮” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵拙徽,是天一觀的道長斋攀。 經(jīng)常有香客問我,道長淳蔼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任鹉梨,我火速辦了婚禮,結(jié)果婚禮上存皂,老公的妹妹穿的比我還像新娘。我一直安慰自己骤菠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布商乎。 她就那樣靜靜地躺著祭阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抹凳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天赢底,我揣著相機與錄音蔗牡,去河邊找鬼。 笑死嘁扼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趁啸。 我是一名探鬼主播督惰,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼访娶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起崖疤,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劫哼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體权烧,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡般码,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了侈询。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖温技,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舵鳞,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布抛虏,位于F島的核電站套才,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏背伴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一息尺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搂誉,春花似錦静檬、人聲如沸并级。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凯旋。三九已至,卻和暖如春至非,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谐鼎。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工趣惠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人味悄。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像唐片,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子费韭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容