阅读 144

细读Vue2.6.14 Core 源码(2): After Vue

细读Vue2.6.14 Core 源码(2): After Vue

    /**
        @date 2021-08-10
        @description 细读Vue2.6.14 Core 源码(2): After Vue
    */
复制代码

壹(序)

之前简单的了解了入口,知道这时候做了引入 Vue构造函数 以及初始化一些 全局API,和一些 ssr 的事情(暂不关注),这篇文章关注一下Vue构造函数的入口文件,以及对Vue做了什么事情

function Vue (options) {
  // 调用_init函数
  this._init(options)
}
// Vue的prototype属性上添加_init
initMixin(Vue)
// Vue的prototype属性上添加 $data(此时为undefined), $props(此时为undefined), $set, $delete, $watch
stateMixin(Vue)
// Vue的prototype属性上添加 $on, $once, $off, $emit方法
eventsMixin(Vue)
// Vue的prototype属性上添加 _update, $forceUpdate, $destroy
lifecycleMixin(Vue)
// Vue的prototype属性上添加 _render, $nextTick, $destroy
renderMixin(Vue)
复制代码

首先是声明 Vue构造函数,然后调用了几个 mixin 函数,下面仔细阅读几个 mixin 函数

贰(initMixin)

Vue函数里面最重要的代码就一句(还有一个警告,这里删掉了): this._init(options),这个 _init 函数,就在 initMixin 中添加的(这里只是声明,调用在后面,所以真正调用的时候再看内部代码):

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // 记录 uid,递增
    vm._uid = uid++

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // 内部组件的 $options,有进行优化提升性能
      initInternalComponent(vm, options)
    } else {
      // $options 属性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // _renderProxy属性,非生产环境会通过 Proxy 重写 has 或 get
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      // 生产环境即为本身
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    /** 
     * 初始化以下值,并根据判断 push 自身到 parent 的 $children
     *   vm.$parent = vm.$options.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
     */
    initLifecycle(vm)
    // 初始化事件绑定,获取 _parentListeners,并绑定到自身 vm 中
    initEvents(vm)
    // 初始化 _node, $node, _staticTrees, $slots, $scopedSlots, $createElement, 设置$attrs, $listeners 为响应式数据
    initRender(vm)
    // 调用 beforeCreate 钩子函数
    callHook(vm, 'beforeCreate')
    // 初始化 inject 配置
    initInjections(vm) // resolve injections before data/props
    // 处理 props, methods, data, computed, watch
    initState(vm)
    // 处理 provide 对象
    initProvide(vm) // resolve provide after data/props
    // 调用 created 钩子函数
    callHook(vm, 'created')

    // 如果未传入 el, 则需要调用 $mount 方法
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
复制代码

叁(stateMixin)

stateMixin 函数用于给 Vue.prototype 添加 $data, $props, $set, $delete, $watch 属性方法

export function stateMixin (Vue: Class<Component>) {
  const dataDef = {}
  // _data 在 initData 中声明
  dataDef.get = function () { return this._data }
  const propsDef = {}
  // _props 在 initProps 中声明
  propsDef.get = function () { return this._props }
  // 向Vue.prototype中写入 $data 和 $props
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  // 向Vue.prototype中写入 $set 和 $delete
  Vue.prototype.$set = set
  Vue.prototype.$delete = del
  // 向Vue.prototype中写入 $watch
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    // cb 为纯对象时,处理之后再调用(createWatcher 中会重新调用 $watch 函数)
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // 声明 watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 立即触发回调
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      // 调用 cb
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    // return unwatch方法
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

复制代码

肆(eventsMixin)

eventsMixin 函数用于添加 $on, $once, $emit, $off 属性方法

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 {
      // 初始化 _events[event] 或 在 _events[event] 初始化后调 push 方法
      (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
    // 内部调用 $off 方法,以实现只调用一次
    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
    // 未提供参数,移除所有事件监听器
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // event 为数组
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    const cbs = vm._events[event]
    // 未找到事件
    if (!cbs) {
      return vm
    }
    // 未传入fn
    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
    let cbs = vm._events[event]
    // _events中能找到 event 则表示有相应的 $on 事件,则调用该事件
    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
  }
}

复制代码

伍(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
    // 设置 activeInstance 
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode

    if (!prevVnode) {
      // 初始渲染
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // 更新渲染
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // 完成重新渲染后,重置 activeInstance 为 null
    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) {
      // 有 _watcher 则调用 _watcher 的 update 方法
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    // 调用 beforeDestroy hook
    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)
    }
    // 处理 _watcher 的卸载
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    // 处理 _watchers 中所欲 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
    // 调用 __patch__ 函数,第二个参数传入 null,销毁当前 vnode
    vm.__patch__(vm._vnode, null)
    // 调用 destroyed hook
    callHook(vm, 'destroyed')
    // 调用 $off ,且不传参,表示移除所有事件监听器
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}
复制代码

陆(renderMixin)

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 向 Vue.prototype 中 写入 _o, _n, _s ...属性
  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) {
      // 如果有 _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 {
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      // 处理 render error
      handleError(e, vm, `render`)
      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)) {
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}
复制代码

终(导航)

细读Vue2.6.14 Core 源码(1): 入口

细读Vue2.6.14 Core 源码(2): After Vue

细读Vue2.6.14 Core 源码(3): initGlobalAPI

细读Vue2.6.14 Core 源码(4): _init

细读Vue2.6.14 Core 源码(5): initState

细读Vue2.6.14 Core 源码(6): defineReactive

细读Vue2.6.14 Core 源码(7): $mount

细读Vue2.6.14 Core 源码(8): $createElement

文章分类
前端
文章标签