vue事件相关实例方法源码分析

vm.$on

—vue实例注册事件的api

Vue.prototype.$on = function (event, fn) {
     
      var vm = this;
      if (Array.isArray(event)) {
     
        for (var 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
    };

首先判断,传入的event是不是个事件数组,如果是的话,以递归的思路,在vue实例上注册事件。若是单个事件字符串,就会去判断该事件有没有注册过,没有注册过就给该事件初始化一个空数组。之后将fn即回调函数压入数组。

vm.$off

—vue实例注销事件的api

    Vue.prototype.$off = function (event, fn) {
     
      var vm = this;
      // all
      if (!arguments.length) {
     
        vm._events = Object.create(null);
        return vm
      }
      // array of events
      if (Array.isArray(event)) {
     
        for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
     
          vm.$off(event[i$1], fn);
        }
        return vm
      }
      // specific event
      var cbs = vm._events[event];
      if (!cbs) {
     
        return vm
      }
      if (!fn) {
     
        vm._events[event] = null;
        return vm
      }
      // specific handler
      var cb;
      var i = cbs.length;
      while (i--) {
     
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
     
          cbs.splice(i, 1);
          break
        }
      }
      return vm
    };

分类讨论的思想。
1.针对没有参数传入的情况,直接置空事件列表。作者的思路是,直接由Object.create的方式生产出一个新的存放事件的vue实例属性。
2.若传入的是一个事件数组,那么思路和on是一样的,递归注销。
3.特判部分(specific event部分),就是说如果你这个事件压根没注册过,直接返回。如果你没有传入想要注销的指定事件的回调,直接把对应的事件数组置空。
4.最后就是针对既传入了事件,又传入了参数的情况。作者的思路是逆序遍历数组,用splice删除事件回调。为什么这样做?假如正着遍历,刚删掉一个数,后面的数自动向前移位,那么我们会跳过一个没遍历过的回调。也就是说除了删最后一个回调,正向遍历,每删一个就会跳过一个回调不处理。

vm.$once

    Vue.prototype.$once = function (event, fn) {
     
      var vm = this;
      function on () {
     
        vm.$off(event, on);
        fn.apply(vm, arguments);
      }
      on.fn = fn;
      vm.$on(event, on);
      return vm
    };

显而易见,once底层是使用on和off实现的。将只触发一次的事件回调挂载到on函数上,即用户自己添加的属性,然后把on函数压入对应的事件数组中。当事件被触发时执行on函数内的逻辑,置空事件数组,以apply的形式执行fn。

vue.$emit

    Vue.prototype.$emit = function (event) {
     
      var vm = this;
      {
     
        var 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 + "\"."
          );
        }
      }
      var cbs = vm._events[event];
      if (cbs) {
     
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        var args = toArray(arguments, 1);
        var info = "event handler for \"" + event + "\"";
        for (var i = 0, l = cbs.length; i < l; i++) {
     
          invokeWithErrorHandling(cbs[i], vm, args, vm, info);
        }
      }
      return vm
    };
  }
  function toArray (list, start) {
     
    start = start || 0;
    var i = list.length - start;
    var ret = new Array(i);
    while (i--) {
     
      ret[i] = list[i + start];
    }
    return ret
  }

toArray函数的作用就是根据提供的尺寸大小start,从后向前复制数组长度-start个数组元素。

  function invokeWithErrorHandling (
    handler,
    context,
    args,
    vm,
    info
  ) {
     
    var res;
    try {
     
      res = args ? handler.apply(context, args) : handler.call(context);
      if (res && !res._isVue && isPromise(res) && !res._handled) {
     
        res.catch(function (e) {
      return handleError(e, vm, info + " (Promise/async)"); });
        // issue #9511
        // avoid catch triggering multiple times when nested calls
        res._handled = true;
      }
    } catch (e) {
     
      handleError(e, vm, info);
    }
    return res
  }

这个函数的逻辑大致就是处理回调。
回到emit主体,首先获取事件对应的回调列表,如果你事件数组长度大于1,它会去调用toArray直接拷贝一份。然后循环遍历数组,处理回调。

你可能感兴趣的:(vue,vue)