事件绑定

HTML事件处理程序

最早期的事件绑定方式,直接在HTML中编写事件处理代码。

基本用法

<button onclick="console.log('点击了')">按钮</button>
<button onclick="alert('Hello')">点击</button>
<div onmouseover="this.style.background='red'" onmouseout="this.style.background=''">
    鼠标移入移出
</div>

调用函数

<button onclick="handleClick()">按钮</button>
<button onclick="handleClickWithEvent(event)">传递事件</button>

<script>
function handleClick() {
    console.log("按钮被点击");
}

function handleClickWithEvent(e) {
    console.log("事件对象:", e);
    console.log("事件类型:", e.type);
}
</script>

HTML事件处理程序的问题

<!-- 问题1:HTML与JS混合,难以维护 -->
<button onclick="if(confirm('确定吗?')){submit()}">提交</button>

<!-- 问题2:时序问题,函数可能未定义 -->
<button onclick="handleClick()">按钮</button>
<script src="external.js" defer></script>

<!-- 问题3:作用域问题 -->
<button onclick="console.log(this)">this指向button</button>

东巴文建议

<!-- 不推荐 -->
<button onclick="doSomething()">按钮</button>

<!-- 推荐 -->
<button id="myButton">按钮</button>
<script>
document.getElementById("myButton").addEventListener("click", doSomething);
</script>

DOM0级事件处理程序

通过元素属性赋值的方式绑定事件。

基本用法

const button = document.querySelector("button");

// 赋值方式绑定
button.onclick = function() {
    console.log("按钮被点击");
};

// 移除事件
button.onclick = null;

this指向

const button = document.querySelector("button");

button.onclick = function() {
    console.log(this);        // button元素
    console.log(this === button);  // true
};

获取事件对象

button.onclick = function(event) {
    console.log(event);       // 事件对象
    console.log(event.type);  // "click"
    console.log(event.target);
};

DOM0级的问题

const button = document.querySelector("button");

// 只能绑定一个处理程序
button.onclick = function() {
    console.log("处理程序1");
};

button.onclick = function() {
    console.log("处理程序2");  // 覆盖了第一个
};

// 点击只输出 "处理程序2"

DOM2级事件处理程序

使用addEventListener和removeEventListener方法。

addEventListener

const button = document.querySelector("button");

// 基本语法
// element.addEventListener(type, handler, options)

button.addEventListener("click", function() {
    console.log("点击了");
});

// 可以绑定多个处理程序
button.addEventListener("click", function() {
    console.log("处理程序1");
});

button.addEventListener("click", function() {
    console.log("处理程序2");
});

// 两个函数都会执行

removeEventListener

const button = document.querySelector("button");

function handleClick() {
    console.log("点击了");
}

// 添加
button.addEventListener("click", handleClick);

// 移除 - 必须是同一个函数引用
button.removeEventListener("click", handleClick);

// 注意:匿名函数无法移除
button.addEventListener("click", function() {
    console.log("无法移除");
});
// button.removeEventListener("click", function() {...});  // 无效

第三个参数

const button = document.querySelector("button");

// 布尔值:是否在捕获阶段触发
button.addEventListener("click", handler, false);  // 冒泡阶段(默认)
button.addEventListener("click", handler, true);   // 捕获阶段

// 配置对象
button.addEventListener("click", handler, {
    capture: false,    // 是否捕获阶段
    once: true,        // 是否只执行一次
    passive: true,     // 是否不会调用preventDefault
    signal: controller.signal  // AbortSignal,用于取消
});

once选项

const button = document.querySelector("button");

// 只执行一次后自动移除
button.addEventListener("click", function() {
    console.log("只执行一次");
}, { once: true });

passive选项

// 提升滚动性能
document.addEventListener("touchstart", function(e) {
    // 不会调用e.preventDefault()
}, { passive: true });

// passive: true 表示不会调用preventDefault
// 浏览器可以立即开始滚动,不用等待JS执行完

AbortController取消事件

const button = document.querySelector("button");
const controller = new AbortController();

button.addEventListener("click", function() {
    console.log("点击");
}, { signal: controller.signal });

// 取消事件监听
controller.abort();

IE事件处理程序

旧版IE使用attachEvent和detachEvent(已废弃)。

IE方式(了解即可)

// IE8及以下
if (element.attachEvent) {
    element.attachEvent("onclick", function() {
        console.log("点击");
    });
}

// 移除
element.detachEvent("onclick", handler);

// 注意:事件名需要加on前缀
// this指向window而不是元素

兼容性封装

const EventUtil = {
    addHandler: function(element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    
    removeHandler: function(element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    }
};

// 使用
EventUtil.addHandler(button, "click", handleClick);
EventUtil.removeHandler(button, "click", handleClick);

事件监听器

深入理解事件监听器的工作原理。

监听器队列

const button = document.querySelector("button");

button.addEventListener("click", function handler1() {
    console.log("handler1");
});

button.addEventListener("click", function handler2() {
    console.log("handler2");
});

button.addEventListener("click", function handler3() {
    console.log("handler3");
});

// 点击时按添加顺序执行: handler1 → handler2 → handler3

同一个函数多次添加

const button = document.querySelector("button");

function handler() {
    console.log("点击");
}

// 同一个函数可以添加多次
button.addEventListener("click", handler);
button.addEventListener("click", handler);
button.addEventListener("click", handler);

// 点击会执行3次

获取事件监听器

// Chrome DevTools方法(非标准)
// getEventListeners(element)

// 在控制台中
// getEventListeners(document.querySelector("button"))

移除事件监听器

正确移除事件监听器的方法。

基本移除

const button = document.querySelector("button");

function handleClick() {
    console.log("点击");
}

button.addEventListener("click", handleClick);

// 移除
button.removeEventListener("click", handleClick);

使用命名函数

// 正确:使用命名函数
function handler() {
    console.log("点击");
}
element.addEventListener("click", handler);
element.removeEventListener("click", handler);

// 错误:匿名函数无法移除
element.addEventListener("click", function() {
    console.log("点击");
});
element.removeEventListener("click", function() {  // 不同的函数
    console.log("点击");
});

使用箭头函数

const button = document.querySelector("button");

// 保存引用
const handler = () => console.log("点击");

button.addEventListener("click", handler);
button.removeEventListener("click", handler);  // 可以移除

// 直接写无法移除
button.addEventListener("click", () => console.log("点击"));
// 无法移除

使用bind

const button = document.querySelector("button");
const obj = {
    name: "东巴文",
    handleClick: function() {
        console.log(this.name);
    }
};

// bind返回新函数
const boundHandler = obj.handleClick.bind(obj);

button.addEventListener("click", boundHandler);
button.removeEventListener("click", boundHandler);  // 可以移除

// 注意:每次bind都是新函数
button.addEventListener("click", obj.handleClick.bind(obj));
button.removeEventListener("click", obj.handleClick.bind(obj));  // 无效!

自动移除模式

// 使用once选项
button.addEventListener("click", handler, { once: true });

// 使用AbortController
const controller = new AbortController();
button.addEventListener("click", handler, { signal: controller.signal });
controller.abort();  // 取消

// 执行一次后移除
function handleOnce() {
    console.log("执行一次");
    button.removeEventListener("click", handleOnce);
}
button.addEventListener("click", handleOnce);

事件绑定对比

方式 多处理程序 可移除 捕获控制 东巴文建议
HTML属性 不推荐
DOM0级 简单 简单场景
DOM2级 需引用 推荐

下一步

掌握了事件绑定后,让我们继续学习:

  1. 事件类型 - 学习各种事件类型
  2. 事件高级 - 学习高级事件处理
  3. Window对象 - 学习BOM基础

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:事件绑定是前端交互的基础,推荐使用addEventListener方式,它支持多个处理程序、可以控制事件流阶段、还能精确移除。在 db-w.cn,我们帮你掌握事件绑定的最佳实践!