本节摘要:事件是用户操作的统一信号——点击、输入、提交、按键各有名字,监听器是接在元素上的响应装置。本节讲监听的注册与解除、事件对象携带的现场信息、事件从内向外冒泡的传播机制,以及事件委托如何用一截线带活一整片区域。看板的按钮点击、表单拦截、状态切换在本节全部通电。
页面有反应,不是浏览器猜出来的,而是行为班提前接好的线。注册监听就是接线:指定元素、指定事件名、指定响应函数。用户操作落在元素上时,浏览器把现场信息打包成事件对象交给响应函数执行。这套机制把"什么时候发生"交给浏览器盯守,"发生之后干什么"留给你写——分工清晰,正是工厂里传感器接进控制柜的路数。
// 监听注册:三要素齐备 const btn = document.querySelector("#submit-btn"); btn.addEventListener("click", function (event) { // 元素:按钮;事件名:click;响应函数:收一个事件对象参数 console.log("按钮被点击", event.timeStamp); }); // 同一元素可挂多个监听:点击一次、多个响应依次执行 function handleClick(event) { /* ... */ } btn.addEventListener("click", handleClick); btn.removeEventListener("click", handleClick); // 解除监听:必须传同一个函数引用——匿名函数接了就拆不掉 // 这就是 4.2 节强调"函数是值"的工程意义之一
// 表单提交拦截:行为班接管提交动作 const form = document.querySelector("#new-order-form"); form.addEventListener("submit", function (event) { event.preventDefault(); // 阻止默认提交:默认行为是整页跳转刷新, // 看板要留在本页处理数据——接管的第一步永远是拦默认 const data = new FormData(form); // 表单数据接口:按字段名收齐全部输入,省去逐个取值 const order = { product: data.get("product").trim(), process: data.get("process"), priority: data.get("priority"), note: data.get("note"), }; console.log("收集到的工单:", order); // 后续:校验、入库、重画页面——本节末的完整接线演示 });
事件不是只落在被点的元素上。一次点击的完整旅程分三段:先从树根向下"捕获"到目标元素,在目标上触发,再从目标向上"冒泡"回树根。默认监听挂在冒泡段——目标处理完,祖先元素依次有机会再处理。这个设计让"祖先代管子孙的事件"成为可能,是下一小节事件委托的原理。
时序图里"冒泡途中每一层都有机会响应"是关键信息。两个推论立刻可用:祖先监听能收到子孙上发生的事件(委托的基础);不想要上层再处理时,响应函数里可以叫停继续上浮(.stopPropagation() 的用途,阻止的就是这个继续传播)。
工单表每行都有一个状态按钮,几十行就几十个按钮。逐个注册监听费线,新增的行还得补接。事件委托换一种接法:监听只挂在表格容器上,点击任何按钮,事件冒泡到容器时统一受理,再从事件对象里查明"到底点的是哪一行的哪个按钮"。接线从几十截减到一截,新增行自动被覆盖——委托是事件系统里最划算的一笔工程投资。
// 事件委托:表格级统一受理 document.querySelector(".order-table").addEventListener("click", function (event) { const btn = event.target.closest("button.status-btn"); // 从实际被点的元素向上找状态按钮:点在按钮内任何部位都找得到 if (!btn) return; // 点的不是按钮:与本次接线无关,放行 const tr = btn.closest("tr"); // 再向上找到所属行 const no = tr.dataset.no; // 行上挂的工单号(4.4 节的 data 属性) console.log("要切换状态的单:", no); // 后续:改数据层状态、重画表格——单点受理完成 }); // 新增的工单行无需再接线:它们冒泡到容器时自动被覆盖
// 事件对象的现场信息速查 form.addEventListener("submit", function (e) { e.preventDefault(); // 拦默认:提交、跳转、滚动等默认行为 // e.stopPropagation(); // 叫停上浮:本层处理完,祖先不再收 }); input.addEventListener("keydown", function (e) { if (e.key === "Enter") { console.log("回车确认"); } // e.key:按键名——快捷键处理的入口 }); link.addEventListener("click", function (e) { e.preventDefault(); console.log(e.currentTarget === link, e.target); // currentTarget:接线者本人(委托场景下与 target 不同) // target:实际被点的源头——委托时靠它辨认真凶 });
把前几节的零件全部接起来,走一遍看板的核心闭环:操作工点状态按钮,工单状态按待开工、加工中、已完成的顺序推进,页面与统计同步更新。数据层(4.3)、渲染函数(4.4)、事件委托(本节)三件合体。
// 状态推进:数据层的流转规则 const NEXT = { pending: "running", running: "done", done: "done" }; function advance(no) { const i = orders.findIndex((o) => o.no === no); if (i < 0) return; const o = orders[i]; if (o.status === "done") return; // 已完工的单不再推进 orders[i] = { ...o, status: NEXT[o.status] }; // 不可变更新:4.3 节的姿势 renderOrders(orders); // 重画表格:4.4 节的投影 renderStatsBar(summarize(orders)); // 重画统计:一份数据两处投影 } // 表格委托接线(渲染函数里给每个状态按钮带上工单号) function renderOrders(list) { const tbody = document.querySelector(".order-table tbody"); tbody.replaceChildren(); for (const o of list) { const tr = document.createElement("tr"); tr.dataset.no = o.no; // 工单号挂在行上,委托时取用 tr.innerHTML = ` <td>${o.no}</td><td>${o.product}</td><td>${o.process}</td> <td>${o.owner}</td> <td><button class="status-btn">推进</button></td> <td><span class="badge badge-${o.status}">${STATUS_LABEL[o.status]}</span></td>`; tbody.appendChild(tr); } } document.querySelector(".order-table").addEventListener("click", (e) => { const btn = e.target.closest("button.status-btn"); if (!btn) return; advance(btn.closest("tr").dataset.no); // 闭包引用 orders:4.2 节的背包 });
闭环走查(对着页面逐条核对): - 点"推进":待开工变加工中,徽章灰变蓝——数据改了、投影跟上 - 再点:加工中变已完成,徽章蓝变绿,统计卡片数字同步减增 - 已完工的单再点:无反应——数据层规则挡在渲染之前 - 新增一行工单:按钮自动可用——委托接线覆盖动态内容 - 全程无一处直接改页面文字:页面永远是数据的投影
常见事件的接线速记:鼠标类点击与双击;键盘类按下与松开,读按键名;表单类提交与值变化、聚焦与失焦——值变化适合做"选完工序自动带出负责人"这类联动;文档类解析完毕与加载完成——前者是接线的黄金时机(元素已齐、图片未到,最早也最稳)。
⚠️ 常见坑:在循环里给上百个元素逐个挂监听,或在删除元素前忘记解除监听。前者换委托,后者养成"删元素先摘线"的习惯——挂在别处的监听引用着已删除的节点,内存就漏了。
💡 关键直觉:写监听前先问两问——这件事挂在哪层最省线(能高则高,委托优先)?响应函数里要用事件对象的哪个字段(目标、按键、值)?两问有答案,接线一次成型。
端子接好了,下一节接外线:异步编程与数据请求,看板连上车间的信息源。