Vue源码之实例方法

个人博客地址
Vue 内部,有一段这样的代码:

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)

上面5个函数的作用是在Vue的原型上面挂载方法。

  • initMixin 函数

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
    
        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
        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
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // 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')
    
        /* 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)
        }
      }
    }

    可以看到在 initMixin 方法中,实现了一系列的初始化操作,包括生命周期流程以及响应式系统流程的启动。

  • stateMixin 函数

    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)
      Object.defineProperty(Vue.prototype, '$props', propsDef)
    
      Vue.prototype.$set = set
      Vue.prototype.$delete = del
    
      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()
        }
      }
    }
    

    stateMixin 被调用时,往Vue的原型上了挂载了三个方法:$delete$set$watch

  • eventsMixin 函数

    export function eventsMixin (Vue: Class<Component>) {
      const hookRE = /^hook:/
      // $on的实现:在注册时把回调函数收集起来,在触发时将收集的事件依次调用
      Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        const vm: Component = this
          
        // 当event为数组时,遍历event将其中的每一项都调用$on
        // 当event为字符串时,向事件列表中添加回调
        // vm._enevts是专门用来存储事件,在initMixin中生成:vm._events = Object.create(null)
        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
        // 当第一次触发自定义事件时,会移除这个事件监听器,然后手动运行fn函数
        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
        // 当参数为空时,直接清空vm._events,移除所有事件监听器
        if (!arguments.length) {
          vm._events = Object.create(null)
          return vm
        }
        // array of events
        // 当event为数组时,遍历event每一项都调用$off
        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
        // 从vm._events中删除这个事件监听器
        let cb
        let i = cbs.length
        while (i--) {
          cb = cbs[i]
          if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1)
            break
          }
        }
        return vm
      }
    
      // $emit的实现:使用事件名event从vm._events中取出事件监听器的回调函数列表
      // 依次执行列表中的回调函数并且把参数传入监听器回调函数
      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
      }
    }
    
    
    
    function invokeWithErrorHandling (
      handler: Function,
      context: any,
      args: null | any[],
      vm: any,
      info: string
    ) {
      let res
      try {
        res = args ? handler.apply(context, args) : handler.call(context)
        if (res && !res._isVue && isPromise(res) && !res._handled) {
          res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
          // issue #9511
          // avoid catch triggering multiple times when nested calls
          res._handled = true
        }
      } catch (e) {
        handleError(e, vm, info)
      }
      return res
    }
    

    eventsMixin 函数被调用时,往 Vue 的原型上挂载了4个方法:$on$once$off$emit

  • lifecycleMixin 函数

    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.
      }
    
      // vm._watcher就是Vue.js实例的watcher,手动执行watcher的update方法
      // 然后组件内部就会重新生成vnode,和旧的vnode进行对比,更新视图
      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
        }
        // 调用钩子函数:beforeDestory
        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
        // 从watcher监听的所有状态的依赖列表中移除watcher
        if (vm._watcher) {
          vm._watcher.teardown()
        }
        // 在Watcher类中有这样一行代码: vm._watchers.push(this)
        // 移除所有用户通过$watch创建的watcher实例
        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
        // 在vnode树上触发destory钩子函数解绑指令
        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
        }
      }
    }

    lifecycleMixin 被调用时,往 Vue 的原型上挂载了三个方法:_updata$forceUpdate$destory

  • renderMixin 函数

    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
      }
    }
    
    // 简化版的nextTick
    
    // 用来存放回调函数事件
    let callbacks = []
    // 表示是否已经添加到微任务列表中
    let pending = false
    // 遍历callbacks,清空callbacks,依次调用回调函数
    function flushCallbacks () {
        penging = false
        const copies = callbacks.slice(0)
        callbacks.length = 0
        for (let i = 0; i < copies.length; i++) {
            capies[i]()
        }
    }
    
    // 把事件添加到微任务列表中去
    let microTimerFunc
    let p = Promise.resolve()
    microTimerFunc = () => {
        p.then(flushCallbacks)
    }
    
    function nextTick (cb?: Function, ctx?: Object) {
      // 一进来先向callback中添加回调事件
      callbacks.push(() => {
          if (cb) {
            cb.call(ctx)
          }
      })
      // 如果pending为false,则表示是第一次执行nextTick,将其添加到微任务中
      // 如果pending为true,表示之前微任务列表中已经添加了这个方法,直接退出
      if (!pending) {
          pending = true
          microTimerFunc()
      }
    }

    renderMixin 被调用时,在 Vue 的原型上挂载了两个方法:$nextTick_render

    原文作者:shisanOnly
    原文地址: https://segmentfault.com/a/1190000020002287
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞