我们好,我是前端小张同学,很久没更新文章了,祝我们2024年万事如意,最近有时间看一些非业务层面的代码,面试官也经常会问道,那今日就跟我们共享一下 Vue的发布订阅方式 以及它的源码完成吧。

1:什么是发布订阅方式?

示例: 微信中的大众号音讯推送,咱们每个用户重视大众号以后大众号会不守时的给咱们推送一些文章,其真实用户和微信大众号之间就现已悄然地势成了一种发布订阅方式。

2: 发布订阅的中心介绍

  1. $on

    功用:担任事情的监听注册,将事以及对应的回调收集到 事情中心,在调用 $emit 的时分 去触发对应的回调。

  2. $off

    功用:担任事情的毁掉和撤销监听,将事情中心的callback 一致铲除 or 指定的 callback 进行铲除

  3. $emit

    功用:担任事情的发射与触发,能够将指定参数传递给回调函数,进行发布告诉。

  4. $once(vue内部拓宽)

    功用:担任监听一个自定义事情,但是只触发一次。一旦触发之后,监听器就会被移除。

3: Vue内部源码完成,它干了 什么事情?

源码文件: vuesrccoreinstanceevents.ts

$on 办法,做了什么事情?

1:在Vue 实例 原型身上挂在 $on办法

2:假如 event 是一个数组 则需要循环注册该事情到 事情中心(vm._events

3:循环注册该事情,然后收集到事情中心

4:判别事情中心是否有该事情映射,假如有则将 传入的 callback push 映射的数组中,假如没有 则 开辟一个数组空间 push

5:假如注册时事情 是 hook 最初的 则 更新 hash符号

  const hookRE = /^hook:/
  Vue.prototype.$on = function (  //在Vue 实例 原型身上挂在 $on办法
    event: string | Array<string>, // 传入 事情称号
    fn: Function // 传入事情的回调函数
  ): Component {
    const vm: Component = this
    if (isArray(event)) { // 假如 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) // 假如 事情中心 _events 取的到 该事情 则 将 callback push 进去 不然 新建一个数组 往里面 push
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) { // 假如注册时事情 是 hook 最初的 则 更新 hash符号
        vm._hasHookEvent = true
      }
    }
    return vm  // 返回实例
  }

$off 办法,做了什么事情?

1: 假如 $off 未提供参数 直接调用 则 直接移除注册中心的一切事情,相当于 清空操作。

2: 假如事情是一个数组 则 循环 移出事情

3: 在vm._events 身上依据 name 取出 callbacks 数组 ,假如没有 则返回 this实例

4: 你在毁掉事情,但callback 不存在,那默许将你映射的数组调集 全部清空,这一步也就对应着Vue官方$off用法,有爱好能够去看看

5: 最终取映射的回调数组 移除 一切的 callback

  // 毁掉一些 注册在控制中心的事情
  Vue.prototype.$off = function (
    event?: string | Array<string>, // 传入 事情称号 or 事情称号数组
    fn?: Function // 移除的函数
  ): Component {
    const vm: Component = this
    // all
    if (!arguments.length) { // 假如 $off 未提供参数 直接调用 则 直接移除注册中心的一切事情
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (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) { // 假如 在事情注册中心取不到 cbs 则 返回this 实例
      return vm
    }
    if (!fn) { // $off 的 callback 都不存在的话 直接移除 事情中心的 callbacks 数组
      vm._events[event!] = null
      return vm
    }
    // specific handler
    let cb // 移除 一切的 callback
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

$emit 办法,做了什么事情?

1: 获得事情中下的映射 回调数组

2: 将取出的数组 , 转换为真实数组,这一步或许是为了鸿沟处理,怕出现类数组的情况

3: 将 arguments 数组转换为 真实数组,作为参数 传递给 invokeWithErrorHandling

4: 循环调用里面的每一个 回调函数,也便是去告诉 正在监听的事情,履行回调

  // 担任 触发 事情中心的
  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (__DEV__) { // 开发环境代码 能够疏忽 
      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 // 转换csb 为真实 数组
      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) // invokeWithErrorHandling 调用里面的每一个 callback 判别每一个 fn 的类型 然后调用
      }
    }
    return vm
  }

$once 办法,做了什么事情?

1: 编写了 内部的 on 办法, 该函数的中心作用 , 履行一次 callback 而且当即毁掉

2: 保存 fn 到 on 身上

3: 手动的内部订阅事情,回调 即为 on 函数,这样能确保 只履行一次,先将 监听的事情 $off 掉,然后手动调用一次 fn 也便是外部传入的 callback

  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 // 保存 当时fn 在 on函数身上
    vm.$on(event, on) // 履行一次调用
    return vm // 返回 实例
  }

总结

其实 从Vue 的源码角度上来看发布订阅方式,其实也没有什么很难的东西,自始至终捋一遍其实你就能够发现 Vue 内部将 一些注册的事情称号 和 callback 存入到一个 名叫 _events 目标身上,其 对应的值为 map 也便是 key Value的方式 , 而经过 $emit 去派发告诉 callback 的履行,经过 $on 取订阅注册事情,以及要触发的回调,但使用过程中,$on 永久真实 mounted 的时分 才干 调用 ,相信我们 看到 vm的时分 现已理解了,this实例的用处,那 $off 对 事情中便是起到了 毁掉和记忆铲除的作用了,由于 已然不需要使用了那就得铲除去,就算是及时的释放资源,更主要的原因是避免重复调用,究竟它是大局的。

结束

好了,以上便是源码的描绘,到这里此篇文章就结束了,那有空我会手写一个 发布订阅方式,带我们一同完成,下方有前端的技术交流群,期望我们能够积极参与进来,一同学习,一同进步,好的,我是前端小张同学 等待你的重视。

面试官:讲一下Vue的发布订阅方式?