Vue Events模块原理分析

Vue中事件大致分为4类

  1. 自定义事件
  2. DOM事件
  3. 组件DOM事件
  4. 组件自定义事件

自定义事件主要由两部分组成

  1. 事件存储器
  2. 绑定事件,触发事件,解绑事件

Vue的每个实例都会有一个_events对象,用来存放本实例上注册的自定义事件,绑定自定义事件的大致流程如下

  1. this.$on 绑定事件
  2. this.$emit 触发事件
  3. this.$off 解绑事件

如果需要给组件绑定原生DOM事件,需要加上native这个修饰符。组件绑定的DOM事件,在父实例解析完毕才开始挂载,遇到子元素是组件,然后去解析内部并生成DOM之后才用addEventListener挂载。

绑定标签DOM事件

对标签绑定DOM事件,会生成如下的渲染函数

with(this) {
    return _c('div', {
        on: {
            click: function() {}
        }
    })
}

根据渲染函数,执行得到VNode,可以看到事件被放到了data上

{
    tag: 'div',
    data: {
        on: {
            click() {}
        }
    }
}

在template解析得到VNode之后,接下来就会进行DOM的生成和挂载,而绑定事件就是发生在开始挂载,创建DOM之后的阶段,挂载是从Vue.prototype._update开始的。绑定DOM事件核心方法就是updateDOMListeners

function updateDOMListeners(oldVnode, vnode) {  
    var on = vnode.data.on || {};    
    var oldOn = oldVnode.data.on || {};
    var target = vnode.elm;
    // 遍历绑定的事件
    for (name in on) {
        newHandler = on[name];
        oldHandler = oldOn[name];   
        // 没有旧事件,就直接添加新事件
        if (typeof oldHandler === "undefined") {
            // 给事件回调包装一层
            target.addEventListener(name, function(){
                on[name]() // 执行保存在vnode的事件
            });
        }        

        // 新事件和旧事件不一样,替换旧事件
        else if (newHandler !== oldHandler) {
            on[name] = newHandler;
        }
    }   

    // 移除旧事件
    for (name in oldOn) {   
        // 旧事件不存在新事件中,直接移除  
        if (typeof on[name] === "undefined") {
            target.removeEventListener(
                name, oldOn[name]
            );
        }
    }
}

自定义事件

Vue的自定义事件是存储在vm._events中,这个作为自定义事件的存储器。它是在_init时候就初始化了

Vue.prototype._init = function(options) {
    initEvents(vm);
}
function initEvents (vm) {
    vm._events = Object.create(null);
}

下面介绍自定义事件4个核心方法

  • $on
  • $off
  • $once
  • $emit
Vue.prototype.$on = function(event, fn) {
    var vm = this;
    // 判断传入的event是否为数组
    // 循环这个数组,给每一个event都绑定回调fn
    if (Array.isArray(event)) {        
        for (var i = 0,l = event.length; i < l; i++) {            
            this.$on(event[i], fn);
        }
    } 
    else { 
        // 如果不是数组,直接添加回调fn
        (vm._events[event] || (vm._events[event] = [])).push(fn);
    }    
    // 为了链式调用
    return vm
};
Vue.prototype.$once = function(event, fn) {
    var vm = this;     
    // 重新包装一下回调
    // 在回调内部触发$off
    function on() {
        vm.$off(event, on);
        fn.apply(vm, arguments);
    }
    on.fn = fn;
    vm.$on(event, on);
    // 为了链式调用
    return vm;
};
Vue.prototype.$emit = function(event) {  
    var vm = this;    
    var _events= event.toLowerCase();    
    var cbs = vm._events[_events]; 
    if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs; 
        var args = toArray(arguments, 1);
        // 遍历回调,一个个调用
        for (var i = 0, l = cbs.length; i < l; i++) {
          cbs[i].apply(vm, args);
        }
    }    
    // 为了链式调用
    return vm
};
Vue.prototype.$off = function(event, fn) {   
    var vm = this;    
    if (!arguments.length) {
        vm._events = Object.create(null);        
        return vm
    }    
    // 递归调用
    if (Array.isArray(event)) {        
        for (var i = 0, l = event.length; i < l; i++) { 
           this.$off(event[i], fn);
        }        
        return vm
    } 
    var cbs = vm._events[event];    
    if (!cbs) return vm    
    if (!fn) {
        vm._events[event] = null;        
        return vm
    }    
    // 去掉特定的函数
    if (fn) {
        var cb;        
        var len = cbs.length;     
        // 遍历移除相应回调
        while (len--) {
            cb = cbs[len];            
            if (cb === fn || cb.fn === fn) {
                cbs.splice(len, 1);                
                break
            }
        }
    }    
    // 为了链式调用
    return vm
};

给组件绑定自定义事件

当我们给组件绑定自定义事件时,会生成这样的VNode

{
    tag: 'test',
    componentOptions: {
        listeners: {
            click($event) {}
        }
    }
}

注册自定义事件的核心方法就是updateComponentListeners

function updateComponentListeners(
    vm, listeners, oldListeners

) {
    var name, cur, old;    
    for (name in listeners) {
        cur = listeners[name];
        old = oldListeners[name];   
        // 没有旧事件,就直接添加新事件
        if (typeof old === "undefined") {
            vm.$on(name, cur);
        }      
        // 新事件和旧事件不一样,替换旧事件
        else if (cur !== old) {
            on[name] = cur;
        }
    }    
    // 移除旧事件
    for (name in oldListeners) {        
        if (typeof listeners[name] === "undefined") {
            vm.$off(name, oldOn[name]);
        }
    }
}

它内部其实也是调用了,自定义事件的$on, $off

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值