vue的事件派发($on、$emit、$once、$off)

971 阅读1分钟

因为面试中被问到了once的实现,所以特地来学习下

  • $on:监听事件
  • $off:移除监听事件
  • $emit:触发事件
  • $once:监听事件,只监听一次

原理

  1. vue示例上会创建一个对象来保存所有要监听的事件: vm._events = {};
  2. 当需要监听一个事件,就会忘vm._events里添加一个键值对,事件的名称为键,一个空数组为值。
  3. 监听事件的回调函数都会添加到对应的数组中,例如调用
vm.$on('event1', cb1);
vm.$on('event1', cb2);
vm.$on('event1', cb3);

// vm._events = { event1: [cb1, cb2, cb3] }
  1. 当调用一处监听函数是,就需要移除其对应函数组中的回调函数
vm.$off('event1', cb1);

// vm._events = { event1: [cb2, cb3] }
  1. 当执行$emit触发事件时,所有的操作就是吧该事件对应数组里的回调函数都拿出来执行一遍
vm.$emit('event1');

// 此时cb2 和 cb3会执行
  1. $once表示该事件只会执行一次,后续在触发就没用了
vm.$on('event2', cb4);
vm.$once('envent2', cb5);
vm.$emit('event2'); // cb4 和 cb5 会执行
vm.$emit('event2'); // cb4会执行,cb5不会执行

这些方法还有一些稍微负责一点的使用方式。

$on(['event1', 'event2'], cb) // 监听多个事件,触发同一个回调
$off(['event1', 'event2'], cb) // 移除多个事件
$off() // 移除事件
$off('event1') // 监听多个事件,触发同一个回调
$emit('event1', params1, parmas2) // 监听多个事件,触发同一个回调

$on

Vue.prototype.$on = function(event, fn) {
    const vm = this
    if(Array.isArray(event)) {
        for(let i = 0,l = enent.length;  i < l; i++) {
            vm.$on(event[i], fn);
        }
        else {
            (vm._events[event] || (vm._events[event] = [])).push(fn);
        }
    }
}

$off

Vue.prototype.$off = function(event, fn) {
    const vm = this;
    
    // 移除所有事件
    if(!arguments.length) {
        vm._events = Object.create(null);
        return vm;
    }
    
    // 如果传入的event是数组,则对该数组里的每个事件在递归调用$off方法
    if(Array,isArray(event)) {
        for(let i = 0,l = enent.length;  i < l; i++) {
            vm.$off(event[i], fn);
        }
        return vm;
    }
    
    const cbs = vm._events[event];
    
    // 如果不存在回调函数,则直接返回,无需其他操作
    if(!cbs) {
        return vm;
    }
    
    // 如果没有指定回调函数,则移除该事件下的所有回调函数
    if(!fn) {
        vm._events[event] = null
        return vm
    }
    let cb;
    let i = cbs.length;
    while(i--) {
        cb = cbs[i];
        if(cb === fn || cb.fn === fn) {
            cbs.splice(i, 1)
            break
        }
    }
    return vm;
}

$emit

Vue.prototype.$off = fucntion(event) {
    const vm = this;
    let cbs = vm._envents[event]
    if(cbs) {
        const args = arguments.slicce(1);
        for(let i = 0, l = cbs.length; i < l; i++) {
            cbs[i].apply(vm, args)
        }
    }
    return vm
}

once

Vue.prototype.$once = function(event, fn) {
    let vm = this;
    function on() {
        // 函数执行一次之后就移除
        vm.$off(event, on)
        
        // 执行on时,执行fn函数
        fn.apply(vm, arguments)
    }
    
    // $off中通过 on.fn === fn来移除事件
    on.fn = fn
    vm.$on(event, on)
    retrn vm
}