event 的实现原理(事件的实现原理)

修饰符:

  • .stop: 调用event.stopPropagation(),即阻止事件冒泡。

  • .prevent: 调用event.preventDefault(),即阻止默认事件。

  • .capture: 添加事件侦听器时使用capture模式,即使用事件捕获模式处理事件。

  • .native: 监听组件根元素的原生事件,即注册组件根元素的原生事件而不是组件自定义事件的。

  • .once: 只触发一次回调

  • .self: 只当事件是从侦听器绑定的元素本身触发时才触发回调。

  • .{keyCode | keyAlias}: 只当事件是从特定键触发时才触发回调。 *.left(2.2.0): 只当点击鼠标左键时触发。 *.right(2.2.0): 只当点击鼠标右键时触发。 *.middle(2.2.0): 只当点击鼠标中键时触发。 *.passive(2.3.0): 以{ passive: true }模式添加侦听器,表示listener永远不会调用preventDefault()。 *.sync(2.6.0):

https://zhuanlan.zhihu.com/p/441130756

.capture 捕获

        <div id="obj1" v-on:click.capture="doc">
            obj1
            <div id="obj2" v-on:click.capture="doc">
                obj2
                <div id="obj3" v-on:click="doc">
                    obj3
                    <div id="obj4" v-on:click="doc">
                        obj4
                        <!--。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。点击obj4的时候,弹出的顺序为:obj1、obj2、obj4、obj3;
                由于1,2有修饰符,故而先触发事件,然后就是4本身触发,最后冒泡事件。
                -->
                    </div>
                </div>
            </div>
        </div>
  1. 冒泡是从里往外冒,捕获是从外往里捕。

  2. 当捕获存在时,先从外到里的捕获,剩下的从里到外的冒泡输出。

即是给元素添加一个监听器,当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发。 就是谁有该事件修饰符,就先触发谁。

dom 事件和自定义事件的区别

parse 阶段处理 ast 树 时 有 processAttrs 方法

通过addHandler 方法,为AST树添加事件相关的属性以及对事件修饰符进行处理

接下来需要将AST语法树转render函数,在这个过程中会加入对事件的处理,首先模块导出了generate函数,generate函数即会返回render字符串,在这之前会调用genElement函数,而在上述addHandler方法处理的最后执行了el.plain = false,这样在genElement函数中会调用genData函数,而在genData函数中即会调用genHandlers函数。

generate

genElement

genHandlers

data最终的表现形式是:

可以看到无论是处理普通元素事件还是组件根元素原生事件都会调用genHandlers函数,genHandlers函数即会遍历解析好的AST树中事件属性,拿到event对象属性,并根据属性上的事件对象拼接成字符串。

事件绑定

前面介绍了如何编译模板提取事件收集指令以及生成render字符串和render函数,但是事件真正的绑定到DOM上还是离不开事件注册,此阶段就发生在patchVnode过程中,在生成完成VNode后,进行patchVnode过程中创建真实DOM时会进行事件注册的相关钩子处理。

invokeCreateHooks就是一个模板指令处理的任务,他分别针对不同的指令为真实阶段创建不同的任务,针对事件,这里会调updateDOMListeners对真实的DOM节点注册事件任务。

最终添加和移除事件都是调用add与remove方法,最终调用的方法即dom的addEventListener和removeEventListener方法。

dom 的更新强制走的是withMacroTask

定义的.nativeOn是Dom原生事件:

普通事件只有自定义事件:

每个事件名都对应一个数组 (vm._event[event] || (vm._events[event] = []) ).push(fn)

在进行事件派发 对事件名下的所有事件进行操作

我们对于同一个事件,可能会注册多个侦听器,也就是多个回调函数 例如$on

this.$emit 往当前子组件派发事件 有个幻觉就是在父元素上面做监听 实际上是在子组件自身的实例上

自定义对象

组件节点中 on vue 自定义事件 就是一个典型的自定义事件中心

非常经典的事件中心的实现,把所有的事件用 vm._events 存储起来,当执行 vm.$on(event,fn) 的时候,按事件的名称 event 把回调函数 fn 存储起来 vm._events[event].push(fn)。当执行 vm.$emit(event) 的时候,根据事件名 event 找到所有的回调函数 let cbs = vm._events[event],然后遍历执行所有的回调函数。当执行 vm.$off(event,fn) 的时候会移除指定事件名 event 和指定的 fn 当执行 vm.$once(event,fn) 的时候,内部就是执行 vm.$on,并且当回调函数执行一次后再通过 vm.$off 移除事件的回调,这样就确保了回调函数只执行一次。

总结: event 在编译阶段生成相关的 data, 对于 Dom 事件在 patch 过程中的创建阶段和更新阶段执行 updateDOMListeners 生成 DOM 事件;对于自定义事件,会在组件初始化阶段通过 initEvent 创建

Vue 支持 2 种事件类型,原生 DOM 事件和自定义事件,它们主要的区别在于添加和删除事件的方式不一样,并且自定义事件的派发是往当前实例上派发,但是可以利用在父组件环境定义回调函数来实现父子组件的通讯。另外要注意一点,只有组件节点才可以添加自定义事件,并且添加原生 DOM 事件需要使用 native 修饰符;而普通元素使用 .native 修饰符是没有作用的,也只能添加原生 DOM 事件。

最后更新于

这有帮助吗?