Vue.js的runtime分析

Vue核心

Vue.js执行了初始化定义(initMixin)、状态管理(stateMixin)、事件(eventsMixin)、生命周期(lifecycleMixin)、渲染(renderMixin)五个方法

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'
import type { GlobalAPI } from 'types/global-api'

function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue)

export default Vue as unknown as GlobalAPI

initMixin

这个方法只做了一件事,就是定义了Vue的初始化流程。

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    //初始化流程
  }
}

注意了只是定义,这是一个匿名函数,不会执行函数里的代码的,我们打开src/core/instance/index.ts文件

function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

会发现这个匿名函数实际是在Vue的构造函数中调用的,也就是new Vue({xxx})时触发。

stateMixin

这个方法定义实现了对Vue.js的dataprops两大数据持有对象的数据劫持,以及定义了观察者模式实现

  const dataDef: any = {}
  dataDef.get = function () {
    return this._data
  }
  const propsDef: any = {}
  propsDef.get = function () {
    return this._props
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

这里需要认真理解this的含义,this代表的是被劫持的对象,也就是vm,这里通过数据劫持使vm上的$data/$props成为了_data/_props的代理对象。
只要在Vue初始化时将传入的data/props实例放到vm_data/_props属性中,就可以愉快的用this.$data获取到data数据了

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

这两行对$set$delete的赋值看着有点莫名其妙,但是他在Runtime中不会执行,所以先不必理会其具体实现。
这两个方法是用来动态增删观察者模式中需要观察的属性的,也就是说代码中data里没有定义的属性,如果也想获得数据绑定能力,可以通过vm.$set()方法设置监听,尔某个属性如果想要解除数据绑定则可以调用vm.$delete()方法解除监听。

  Vue.prototype.$watch = function (
    expOrFn: string | (() => any),
    cb: any,
    options?: Record<string, any>
  ): Function {
    //观察者实现  
  }

这里为vm$watch属性定义了观察者模式实现,同样Runtime中不会执行,先不必理会。

eventsMixin

这个方法定义了Vue.js设计的几个事件的实现。

  const hookRE = /^hook:/
  Vue.prototype.$on = function (
    event: string | Array<string>,
    fn: Function
  ): Component {
    const vm: Component = this
    if (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
  }

$on方法是用来添加监听事件的,比如说v-on:click="handle()",就会调用vm.$on()来新增一个click监听,发生click事件的时候,则会触发handle()函数,click是浏览器的事件,我们也可以添加自定义的事件监听。
这里的核心是设计了一个事件维护对象_events,组件中所有的事件都会被存放到这个对象中

  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
  }

$once方法可以直接看其实现,当事件产生的时候,会立即注销该监听函数的监听,然后执行监听函数,达到只执行一次的效果。

  Vue.prototype.$off = function (
    event?: string | Array<string>,
    fn?: Function
  ): Component {
    //注销监听函数实现
  }

$off方法在$once方法中用到了,是用来注销监听函数的。
具体实现是找到事件名对应的所有监听函数,将想要注销的监听函数从对象中移除。

 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
 }

$emit方法会将传入的事件名对应的所有监听函数都执行一遍。
这里有一个arguments变量,是一个表示所有入参的数组,别看函数入参只有一个event,但实际的入参数据是放在event参数后传入的,事件对应的监听函数入参会接收到$emit方法的第二到最后一个参数。

这四个事件相关的函数构成了Vue.js的事件系统

lifecycleMixin

这个方法定义了生命周期中页面的更新、强制更新、销毁实现

  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    //更新页面实现
  }

vm._update可以触发

  Vue.prototype.$forceUpdate = function () {
    //强制观察者更新
  }
  Vue.prototype.$destroy = function () {
    //销毁页面
  }

renderMixin

这个方法定义了页面渲染的一些功能

  installRenderHelpers(Vue.prototype)

这里会在vm上设置一些对VNode的操作类,方便_render中调用

  Vue.prototype.$nextTick = function (fn: (...args: any[]) => any) {
    //
  }

$nextTick方法可以注册一个DOM更新完成时的回调函数。
因为DOM不是时刻都在更新的,而是会异步将该一段时间内的变化合并后再更新到DOM,所以我们修改了data中的数据后是无法在对应位置的DOM上立马获取到变更后数据的,我们需要使用$nextTick达成在一个事件循环后再获取DOM数据的目的。

  Vue.prototype._render = function (): VNode {
    //页面解析实现
  }
}

Vue.js的runtime就设计了这么些API,虽然我们还没有对每个API的实现进行研究,但凭借Vue.js的开发经验,能够对整体的功能设计有一个大致的了解,只有真正理解了上述内容,才能在阅读Vue初始化流程的时候不迷茫、不迷惑、不迷路。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容