事件流的三个阶段

当一个事件发生时,它会经历三个 phase(阶段):

  1. 捕获阶段(Capture Phase):从window向下到目标元素

  2. 目标阶段(Target Phase):到达实际的目标元素

  3. 冒泡阶段(Bubble Phase):从目标元素向上回到window

捕获阶段: window → document → html → body → parent(父元素) → target(目标)
目标阶段: target
冒泡阶段: target → parent → body → html → document → window

什么是事件委托?

事件委托是一种利用事件冒泡机制,将事件处理程序绑定在父元素上,而不是每个子元素上,通过事件目标(event.target)来判断具体是哪个子元素触发事件的技术。

传统方式 vs 事件委托

传统方式(为每个元素绑定事件)

// 为每个按钮单独绑定事件
const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {
    button.addEventListener('click', function() {
        console.log('按钮被点击:', this.textContent);
    });
});

// 问题:动态添加的按钮没有事件
const newButton = document.createElement('button');
newButton.className = 'btn';
newButton.textContent = '新按钮';
document.body.appendChild(newButton);
// 这个新按钮没有点击事件!

事件委托方式(在父元素上绑定一个事件)

// 在父元素上绑定一个事件
document.addEventListener('click', function(event) {
    // 检查点击的是否是.btn元素
    if (event.target.classList.contains('btn')) {
        console.log('按钮被点击:', event.target.textContent);
    }
});

// 现在动态添加的按钮自动拥有事件
const newButton = document.createElement('button');
newButton.className = 'btn';
newButton.textContent = '新按钮';
document.body.appendChild(newButton);
// 这个新按钮自动拥有点击事件!

事件委托的语法

基本模式

parentElement.addEventListener('click', function(event) {
    if (event.target.matches(selector)) {
        // 处理事件
    }
});

事件委托的原理

事件委托利用的是事件冒泡机制:当子元素发生事件时,这个事件会向上"冒泡"到所有祖先元素。我们可以在父元素上监听事件,然后通过判断 event.target 来知道具体是哪个子元素触发的事件。

比如所学生的作业都交到老师那,然后老师批改作业根据作业的名字来判断是哪个学生的作业。

传统方式(效率低)

// 就像:每个学生单独找老师交作业
const 学生们 = document.querySelectorAll('.学生');

学生们.forEach(学生 => {
    学生.addEventListener('click', function() {
        console.log(`批改${this.dataset.名字}的作业`);
        // 需要为每个学生单独设置
    });
});

// 新学生来了,要单独处理
const 新学生 = document.createElement('div');
新学生.className = '学生';
新学生.dataset.名字 = '新同学';
新学生.textContent = '新同学的作业';
新学生.addEventListener('click', 处理作业); // 额外工作

事件委托(效率高)

// 就像:老师设立一个作业收集箱
const 班级 = document.querySelector('.班级');

班级.addEventListener('click', function(事件) {
    // 检查交作业的是不是学生
    if (事件.target.classList.contains('学生')) {
        // 查看作业上的名字
        const 学生名字 = 事件.target.dataset.名字;
        console.log(`批改${学生名字}的作业`);
    }
});

// 新学生来了,直接交作业就行
const 新学生 = document.createElement('div');
新学生.className = '学生';
新学生.dataset.名字 = '新同学';
新学生.textContent = '新同学的作业';
班级.appendChild(新学生);
// 自动就能被处理,不需要额外设置!
  1. 传统方式
    只在页面现有元素上一次性绑定监听器。
    后面通过 innerHTMLappendChildAjax 等方式新增的元素当时并不存在,自然没有被绑定,所以需要再手动绑一次。

  2. 事件委托
    把监听器绑在父级(或更高祖先)上,而父级一开始就存在。
    无论子元素是
    页面初始化时
    还是后来动态添加的,只要事件最终能冒泡到父级,父级就能统一捕获并处理,因此不需要再针对新元素单独处理。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐