c++项目实例_Vue源码阅读连载之Vue实例

9b2130c7ab4053c32e2258eb5a81fd03.png

我们学习Vue都是从下面这个例子开始的

new Vue({
  render: h => h(App),
}).$mount('#app')

事实上,所有的Vue项目的组成组件都是一个Vue的实例,最后由根部的Vue实例去挂载到DOM上,当然这个"挂载"的操作可以针对不同的平台而有不同的行为,比如挂载到移动设备上就成了weex,挂载到小程序上就成了mpvue。所以首先我们要知道Vue实例里面含有哪些东西。

实际上,这个$mount就等同于React里的ReactDOM.render。

Vue构造函数

我是全局搜索关键词"function Vue"来定位到Vue类的定义的,位于/src/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)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

代码很简单,就是用function的方式定义了Vue类,之后只要new Vue一下就可以生成Vue的实例了。在构造函数里我们看到调用了_init( )方法,但是上下文中并没有看到_init( )方法的定义,往下看,有一堆的mixin方法,这些方法其实就是在Vue的prototype上增加各种方法,_init( )也就是在这些mixin调用过后添加的。

列举一下Vue里约定俗成前缀

_ 表示私有,这个和一般的约定一致。

$ 表示实例上的属性或方法,比如$mount。

另外还有一些方法是定义在全局的,也就是Vue构造函数上的,比如Vue.component。

initMixin

刚才看到的_init( )方法就是在这里定义出来的,抽掉一些不重要的代码后

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    vm._uid = uid++

    vm._isVue = true
    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        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')

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

这一段执行完成后,相当于生命周期的

be92e97c7794918db7260f559aeb0f06.png

大致干了这些事情

对于每一个生成的Vue实例在内部都用_uid来跟踪

合并option对象

initLifecycle方法里定义了许多和生命周期有关的变量,有些是内部的,有些是暴露在实例上的

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

  // locate first non-abstract parent
  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
}

initEvents针对配置里传进来事件,做了一些操作。

initRender里初始化了一些跟虚拟DOM渲染有关的属性和方法,比如后面会登场的赫赫有名的_c方法和实例上的$createElement方法,而把方法连接到实例上其实就是为了在渲染的时候能获得实例的上下文

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

}

接着callHook方法第一次登场,从名字就可以看出来callHook就是用来调用钩子函数。

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

它还是通过$emit来调用的,所以在后面的章节要睁大眼睛看一下Vue里事件的实现。

下面的Injection和Provider有点陌生,查了下官网上说

provideinject主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

而夹在中间的initState( )又是重中之重,初始化了Vue的几大响应式组成部分,计划下一篇文章就是探究这方面的原理的。

最后如果找到配置项里有el,就执行挂载的方法。当然也可以如之前的代码所示,调用Vue实例上的$mount来执行挂载,对应到生命周期图里的这个部分。

13beafb2c5fe6304107b60d2b4f13568.png

stateMixin

这里面定义了一个响应式里很重要的方法——$watch,也会放到后文来详述。

eventsMixin

实例上事件的四大方法

  • vm.$on
  • vm.$once
  • vm.$off
  • vm.$emit

先看$on

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)
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

其实就是把传入的方法回调,放在Vue的实例上,而事件的名字就作为键,对应的值是一个数组,表明同一个事件可以调用多个回调方法,传入的event也能是一个数组,不过这种不同的事件上调用同一个回调的事情现实中应该不多吧。

可以做一下实验

e46926e1c421392ef72b596123ccaf17.png

有$on就必有$off

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
  }

原理也很简单,就是寻找——删除。

而$once本质就是在这个事件回调上做了替换,先调用一下这个传入的回调方法,执行完成后调用一下$off把它删除,达到只调用一次的目的。

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
  }

$emit的话就是寻找——调用。

Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    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
  }

注意一下这几个方法调用返回的都是Vue实例本身,说明实际中可以写成链式调用的形式。

lifecycleMixin

定义了$forceUpdate,强行在Vue实例上的所有Watcher对象都调用一把更新,后面讲响应式原理的时候会谈到什么是Watcher对象。

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

又定义了$destroy

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

对应了生命周期图上的这个部分

147a59d79fb7e75c79e54a4ee4da09e8.png

清理工作还是挺多的

  1. 和父组件的引用关系中拆分出来
  2. 清理所有Watcher对象
  3. 清理所有Observer对象
  4. 移除所有事件
  5. 清理挂载的DOM元素

renderMixin

主要是定义了跟虚拟DOM渲染有关的属性($vnode)和私有方法(_render),在后面讲到虚拟DOM的时候再来讲。

下一篇文章要讲Vue响应式原理了,想想都激动。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值