vue源码之 eventBus原理

VUE中eventBus可以用来进行任何组件之间的通信,我们可以把eventBus当成一个管道,这个管道两端可以接好多组件,两端的任何一个组件都可以进行通信。其实这个管道就是Vue实例,实例中的$on, $off, $emit方法来实现此功能。先通过简单例子看看eventBus怎么用。

<script src="./vue.js"></script>
<div id="app">
    <child-comp-1></child-comp-1>
    <child-comp-2></child-comp-2>
</div>
<script>
 
    var eventBus = new Vue();
 
    Vue.component('child-comp-1', {
        template: "<div></div>",
        mounted: function () {
            eventBus.$on("event", function(value){
                console.log("child-comp-1 receive event: " + value);
            });       
        }
    });
 
    Vue.component('child-comp-2', {
        template: "<div></div>",
        mounted: function () {
               eventBus.$emit("event", "here is comp2 msg");
        }
    });
 
    const app = new Vue({
        el: '#app'
    });
</script>

我们实例化了一个Vue实例,并赋值给了eventBus全局变量。那么我们就可以在任何地方使用这个全局变量。接下来我们看看这个Vue实例中的$on 和 $emit方法。

  function eventsMixin (Vue) {
    var hookRE = /^hook:/;
    Vue.prototype.$on = function (event, fn) {		// 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);
        if (hookRE.test(event)) {
          vm._hasHookEvent = true;
        }
      }
      return vm
    };
 
    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
    };
 
    Vue.prototype.$off = function (event, fn) {
    /* 三种情况:
    	1、如果没有提供参数,则移除所有的事件监听器
    	2、只提供事件,则移除该事件上的所有的监听器
    	3、如果同时提供了事件与回调,则只移除这个回调的监听器
    */
      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) {	// cb.fn === fn $once的时候
          cbs.splice(i, 1);
          break
        }
      }
      return vm
    };
 
    Vue.prototype.$emit = function (event) {
      var vm = this;
      var cbs = vm._events[event];
      if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        var args = toArray(arguments, 1);	// toArray的作用:将类似于数组的数据转换成真正的数组,他的第二个参数时起始位置,也就是说args是一个数组,里面包含了第一个参数之外的所有参数
        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
    };
  }

用 _events 存储事件,在new Vue()时,vue执行this._init方法进行一系列初始化操作,其中vue.js实例上创建一个_events属性:vm._events=Object.create(null)

在移除监听器的时候,需要将用户提供的监听器函数与列表中的监听器函数进行对比,相同部分会被移除,导致我们使用拦截器注入到事件列表中时,拦截器和用户提供的函数不相同的,此时用户使用vm.$off来移除监听器,移除操作会失败。
解决方法:将用户提供的原始监听器保存在拦截器的fn中 cb.fn === fn 比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值