21 Vue的事件绑定原理

原生事件绑定是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的$on 实现的。如果要在组件上使用原生事件,需要加.native 修饰符,这样就相当于在父组件中把子组件当做普通 html 标签,然后加上原生事件。

$on$emit 是基于发布订阅模式的,维护一个事件中心,on 的时候将事件按名称存在事件中心里,称之为订阅者,然后 emit 将对应的事件进行发布,去执行事件中心里的对应的监听器

EventEmitter(发布订阅模式--简单版)

    // 手写发布订阅模式 EventEmitter
    class EventEmitter {
      constructor() {
        this.events = {};
      }
      // 实现订阅
      on(type, callBack) {
        if (!this.events) this.events = Object.create(null);
    
        if (!this.events[type]) {
          this.events[type] = [callBack];
        } else {
          this.events[type].push(callBack);
        }
      }
      // 删除订阅
      off(type, callBack) {
        if (!this.events[type]) return;
        this.events[type] = this.events[type].filter(item => {
          return item !== callBack;
        });
      }
      // 只执行一次订阅事件
      once(type, callBack) {
        function fn() {
          callBack();
          this.off(type, fn);
        }
        this.on(type, fn);
      }
      // 触发事件
      emit(type, ...rest) {
        this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
      }
    }
    
    
    // 使用如下
    const event = new EventEmitter();
    
    const handle = (...rest) => {
      console.log(rest);
    };
    
    event.on("click", handle);
    
    event.emit("click", 1, 2, 3, 4);
    
    event.off("click", handle);
    
    event.emit("click", 1, 2);
    
    event.once("dbClick", () => {
      console.log(123456);
    });
    event.emit("dbClick");
    event.emit("dbClick");

源码分析

  1. 原生 dom 的绑定
  • Vue 在创建真是 dom 时会调用 createElm ,默认会调用 invokeCreateHooks
  • 会遍历当前平台下相对的属性处理代码,其中就有 updateDOMListeners 方法,内部会传入 add 方法
    function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) { 
        if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) { 
            return 
        }
        const on = vnode.data.on || {} 
        const oldOn = oldVnode.data.on || {} 
        target = vnode.elm normalizeEvents(on) 
        updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context) 
        target = undefined 
    }
    function add ( name: string, handler: Function, capture: boolean, passive: boolean ) {
        target.addEventListener( // 给当前的dom添加事件 
            name, 
            handler, 
            supportsPassive ? { capture, passive } : capture 
        ) 
    }

vue 中绑定事件是直接绑定给真实 dom 元素的

  1. 组件中绑定事件
    export function updateComponentListeners ( vm: Component, listeners: Object, oldListeners: ?Object ) {
        target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
        target = undefined 
    }
    function add (event, fn) { 
        target.$on(event, fn) 
    }

组件绑定事件是通过 vue 中自定义的 $on 方法来实现的

Last Updated:
Contributors: leeguooooo