手写一个EventBus事件处理中心(解读vue的事件方法)

vue中的事件相关的方法

扒一扒Vue的源码,vue中事件相关的方法,无非这几个,vm.$on, vm.$off, vm.$once, vm.$emit。通过eventsMinxin方法挂在Vue的原型上,本文就是通过解读vue的源码,实现一个简单的事件处理中心类 EventBus。

首先,我们来回顾下这几个方法的用法:

vm.$on( event, callback )

参数:

  • {string | Array<string>} event (数组只在 2.2.0+ 中支持)
  • {Function} callback

vm.$once( event, callback )

参数:

  • {string} event
  • {Function} callback

vm.$off( [event, callback] )

参数:

  • {string | Array<string>} event (只在 2.2.2+ 支持数组)
  • {Function} [callback]

vm.$emit( eventName, […args] )

参数:

  • {string} eventName
  • [...args]

触发当前实例上的事件。附加参数都会传给监听器回调。

实现一个EventBus

首先,我们先定义一个全局的类EventBus。

function EventCenter() {
    // 全局定义一个_events属性,存储事件
    this._events = Object.create(null);
} 
// 通过eventMixin方法在EventCenter的原型上挂载方法
eventMixin(EventCenter);

export default EventCenter;

接下来,我们来实现eventMixin方法。

export default function eventMixin(EventCenter) {            
     EventCenter.prototype.on = function(event, fn) {...}
     EventCenter.prototype.off = function(event, fn) {...}
     EventCenter.prototype.once = function(event, fn) {...}
     EventCenter.prototype.once = function(event) {...}
}

ec.on( event, fn ), event值可以为数组和字符串,fn为事件触发时的回调函数

EventCenter.prototyoe.on = function(event, fn) {
    const ec = this;
    if (Array.isArray(event)) {
        for (let i = 0; i < event.length; i++) {
            ec.on(event[i], fn)
        }
    } else {
        (ec._events[event] || (ec._events[event] = [])).push(fn);
    }
}

ec.off( event, fn ), event值可以为数组和字符串,fn为事件触发时的回调函数

EventCenter.prototyoe.on = function(event, fn) {
    const ec = this;
    // 判断如果不传参数, 则移除所有事件
    if (!arguments.length) {
        ec._events = Object.create(null);
    }
    // event为数组时,遍历移除事件
    if (Array.isArray(event)) {
        for(let i = 0; i < event.length; i++) {
            ec.off(event[i], fn);
        }
        return ec;
    }
    const cbs = ec._events[event];
    // 回调不存在 直接返回
    if (!cbs) {
        return ec;
    }
    // cbs为一个或者fn不存在,ec._events[event] = null, 直接移除
    if (arguments.length === 1) {
        ec._events[event] = null;
        return ec;
    }
    if (!fn) {
        ec._events[event] = null;
        return ec;
    }
    // 否则,遍历cbs,移除cbs中为fn的回调函数
    let cb;
    let i = cbs.length;
    // 从后向前遍历,移除当前监听器时,不会影响未遍历过的监听器的位置。
    while (i--) {
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1);
            break;
        }
    }
    return ec;
}

ec.once( event, fn ), event值可以为字符串,fn为事件触发时的回调函数

const ec = this;
// 自定义一个_on方法,先解绑_on, 然后通过调用apply方法执行fn。
function _on() {
    ec.off(event, _on);
    fn.apply(ec, arguments);
}
// 这句暂时没看懂,等之后查了资料补充
_on.fn = fn;
ec.on(event, _on);
return ec;

ec.emit( event, ...args ), event值可以为字符串,...args为调用时的传参

EventCenter.prototype.emit = function(event) {
    const ec = this;
    let cbs = ec._events[event];
    if (cbs) {
        // 拿到传参
        const args = Array.from(arguments).slice(1);
        for(let i = 0; i < cbs.length; i++) {
            try {
                cbs[i].apply(ec, args);
            } catch(e) {
                new Error('error');
            }
        }
    }
    return ec;
}

至此,一个简单的事件中心就完成了。

参考: vue源码 core/instance/events.js