Skip to content
On this page

合成事件及底层原理

合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个 API,这样做是为了确保事件在不同浏览器中显示一致的属性!

合成时间的基本操作

基础语法

在JSX元素上,直接基于 onXxx={函数} 进行事件绑定! 浏览器标准事件,在React中大部分都支持

https://developer.mozilla.org/zh-CN/docs/Web/Events#%E6%A0%87%E5%87%86%E4%BA%8B%E4%BB%B6

jsx

import React, { Component } from "react";
export default class App extends Component {
    state = {
        num: 0
    };
    render() {
        let { num } = this.state;
        return <div>
            {num}
            <br />
            <button onClick={(ev) => {
                // 合成事件对象 :SyntheticBaseEvent
                console.log(ev); 
                this.setState({
                    num: num + 1
                });
            }}>处理</button>
        </div>;
    }
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

合成事件中的this和传参处理

在类组件中,我们要时刻保证,合成事件绑定的函数中,里面的this是当前类的实例! 也需要保障传参的正常!

jsx
import React, { Component } from "react";
export default class App extends Component {
    handler1 = (ev) => {
        console.log(ev, this);
    };
    handler2 = (x, y, ev) => {
        console.log(x, y, ev, this);
    };
    render() {
        return <div>
            <button onClick={this.handler1}>测试1</button>
            <button onClick={this.handler2.bind(null, 10, 20)}>
                测试2
            </button>
        </div>;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

JS中的事件委托(事件代理)

事件和事件绑定

  • 事件是浏览器内置行为
  • 事件绑定是给事件行为绑定方法
    • 元素.onxxx=function…
    • 元素.addEventListener('xxx',function(){},true/false)

事件的传播机制

  • 捕获 CAPTURING_PHASE
  • 目标 AT_TARGET
  • 冒泡 BUBBLING_PHASE

阻止冒泡传播

  • ev.cancelBubble=true 「<=IE8」
  • ev.stopPropagation()
  • ev.stopImmediatePropagation() 阻止监听同一事件的其他事件监听器被调用

事件委托(代理),就是利用事件的“冒泡传播机制”实现的

例如:给父容器做统一的事件绑定(点击事件),这样点击容器中的任意元素,都会传播到父容器上,触发绑定的方法!在方法中,基于不同的事件源做不同的事情!

  • 性能得到很好的提高「减少内存消耗」
  • 可以给动态增加的元素做事件绑定
  • 某些需求必须基于其完成
JavaScript
const container = document.querySelector('.container'),
    box = document.querySelector('.box');
container.addEventListener('click', function(ev) {
    let target = ev.target,
        targetTag = target.tagName,
        targetClass = target.classList;
    if (targetTag === 'BUTTON' && targetClass.contains('submit')) {
        console.log('我是按钮');
        return;
    }
    if (targetTag === 'A' && targetClass.contains('link')) {
        console.log('我是超链接按钮');
        return;
    }
    console.log('我是新增的PBOX');
});

box.onclick = function(ev) {
    console.log('我是DIV盒子');
    ev.stopPropagation();
};

setTimeout(() => {
    const pBox = document.createElement('p');
    pBox.className = 'p-box';
    pBox.innerHTML = '我是P段落盒子';
    container.appendChild(pBox);
}, 2000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

合成事件的底层机制

总原则:基于事件委托实现 React17及以后,是委托给#root元素

javaScript
const outer = document.querySelector('.outer'),
    inner = document.querySelector('.inner');
/* 原理 */
const dispatchEvent = function dispatchEvent(ev) {
    let path = ev.path,
        target = ev.target;
    [...path].reverse().forEach(elem => {
        let handler = elem.onClickCapture;
        if (typeof handler === "function") handler(ev);
    });
    path.forEach(elem => {
        let handler = elem.onClick;
        if (typeof handler === "function") handler(ev);
    });
};
document.addEventListener('click', function (ev) {
    dispatchEvent(ev);
}, false);

......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

事件对象池

16版本中,存在事件对象池

  • 缓存和共享:对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能!
  • 使用完成之后,释放对象「每一项内容都清空」,缓存进去!
  • 调用 event.persist() 可以保留住这些值!

17版本及以后,移除了事件对象池!

javaScript
syntheticInnerBubble = (syntheticEvent) => {
    // syntheticEvent.persist();    
    setTimeout(() => {
        console.log(syntheticEvent); //每一项都置为空
    }, 1000);
};
1
2
3
4
5
6

click延迟和Vue中的事件处理机制

click事件在移动端存在300ms延迟

  • pc端的click是点击事件
  • 移动端的click是单击事件
JavaScript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// 解决移动端300ms延迟
import fastclick from 'fastclick';
fastclick.attach(document.body);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <App />
);
1
2
3
4
5
6
7
8
9
10
11
12

也可以自己基于touch事件模型去实现

JavaScript
const box = document.querySelector('.box');
box.ontouchstart = function (ev) {
    let point = ev.changedTouches[0];
    this.startX = point.pageX;
    this.startY = point.pageY;
    this.isMove = false;
};
box.ontouchmove = function (ev) {
    let point = ev.changedTouches[0];
    let changeX = point.pageX - this.startX;
    let changeY = point.pageY - this.startY;
    if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
        this.isMove = true;
    }
};
box.ontouchend = function (ev) {
    if (!this.isMove) {
        console.log('点击触发');
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Vue中的事件处理机制

核心:给创建的DOM元素,单独基于addEventListener实现事件绑定 Vue事件优化技巧:手动基于事件委托处理

JavaScript
<template>
  <div id="app">
    <ul class="box" @click="handler">
      <li
        class="item"
        v-for="(item, index) in arr"
        :data-item="item"
        :key="index"
      >
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      arr: [10, 20, 30],
    };
  },
  methods: {
    handler(ev) {
      let target = ev.target;
      if (target.tagName === "LI") {
        console.log(target.getAttribute("data-item"));
      }
    },
  },
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
沪ICP备20006251号-1