美文网首页
Vue.js的runtime分析

Vue.js的runtime分析

作者: zacone | 来源:发表于2022-09-08 17:37 被阅读0次

    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初始化流程的时候不迷茫、不迷惑、不迷路。

    相关文章

      网友评论

          本文标题:Vue.js的runtime分析

          本文链接:https://www.haomeiwen.com/subject/ivsjnrtx.html