Skip to content

07.事件合成机制原理分析

可能很多同学还不太清楚事件合成机制是什么,也不清楚为什么需要掌握事件合成机制。在解答这些疑惑之前,我们先来看一看我们之前所编写的代码:

js
//react-dom.js
function setPropsForDOM(dom, VNodeProps = {}) {
    if(!dom) return
    for (let key in VNodeProps) {
        if (key === 'children') continue;
        if (/^on[A-Z].*/.test(key)) { 
            // TODO
        }
        //...
    }
}

这段代码是为我们根据虚拟DOM所创建出的真实DOM,绑定相关属性,如果有同学已经遗忘setPropsForDOM这个函数的功能,可以回放前面的视频进行复习。当时我们是留了一个TODO项,这个TODO项就是处理DOM所绑定的事件。我们可能会很自然的想到,给某个DOM绑定事件很简单啊,不就是日常开发中我们常用的下面几种方式吗(事件类型很多,这里我们以最常见的点击事件为例):

js
let element = document.getElementById('btn');
// 方式1: 
element.onclick = function(){}
// 方式2: 
element.addEventListenner('click', function(){})

如果我们在实现react的代码中也以上面的方式进行事件绑定会至少有两个问题:

  1. 不同浏览器关于事件的行为有所差异,代码不好维护
    1. 获取事件目标,也就是事件源
      1. w3c浏览器:event.target
      2. IE6、7、8: event.srcElement
    2. 阻止冒泡
      1. 在微软的模型中你必须设置事件的cancelBubble的属性为 true
      2. 在 W3C 模型中你必须调用事件的stopPropagation()方法
    3. 取消默认事件
      1. window.event.returnValue = false;
      2. event.preventDefault();
  2. 每个有事件的dom元素都需要进行一次事件绑定,占用内存大,操作繁琐

而这两个问题都可以通过事件合成的方式来解决。我们先来观察这张图: 事件流

从图中可以看出,dom事件流包括三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

先后顺序是,由外而内,进行事件捕获,随后 事件源头 接收到事件,最后由内而外进行冒泡

而事件合成机制,对传统dom事件流进行了整合:把原本需要绑定在子元素的事件委托给父元素,让父元素负责事件监听,这个过程我们称之为事件委托或事件代理。而实现事件合成机制,充分利用了dom事件流中的事件冒泡阶段。

从DOM事件流这张图片可以看出,我们将事件统一注册到document上,事件源的事件触发后,事件冒泡到document后再做统一的处理。当然我们也可以不注册到document上,但肯定也会注册到某个根节点上吗,其实现原理没有发生任何变化。

如果到了这里还感觉抽象,也不用太担心,下一小节就开始实现我们的事件合成机制代码实现,会有更加直观的感受。

基于 VitePress 构建