事件委托
售票窗口有一条票带。父元素是 #strip。上面一开始就有两个按钮:#red,文字是 red;#blue,文字是 blue。票带下面的 #note 还写着 blank。
不必给每个按钮各挂一个监听器。在票带上调用一次 addEventListener 就够。带子里面的按钮被点击后,点击会往上走到票带。event.target 是那个按钮。event.currentTarget 是票带,也就是这个监听器所在的元素。
按钮 #gold 一开始不在页面上。监听器存好之后才创建它,接到票带上,再点击它。同一个函数照样会跑。纸条变成 gold。页面脚本先点 red,再点 blue,再点 gold。addEventListener 只调用了一次,所以 hands 是 1。票带下面这一行是 red / blue / gold / 1。这些按钮是 type="button"。这里不调用 preventDefault,也不调用 stopPropagation。那是牌子那一页的事,那一页也没有在监听器之后再加子元素。
一张票一个监听器就够了
牌子那一页把参数叫做 event,在墙里面的链接上读 type、target 和 currentTarget。那一行是 click / seal / plaque / true / quiet。衣帽钩那一页把 wool 和 silk 存下来,函数不带参数。那一行是 wool silk / 2 / peg。这里参数仍然叫 event,但 target 干的是另一件事:指出点中的是哪个子元素,哪怕这个子元素当时还不存在。
getElementById 只给你票带、红按钮、蓝按钮和纸条。gold 由 createElement 生出来。red 上一个监听器、blue 上另一个,会漏掉 gold,除非 appendChild 之后再挂一次。票带上的这一个监听器不需要第二次挂载。在函数里面读 currentTarget。函数返回之后,这个属性是 null。
票带记下什么
纸条从 blank 开始。按钮上的字一直是 red 和 blue,gold 稍后才出现。按顺序读每一行。除了页面那一行(下面的演示)以外,每一段都从两个按钮的票带开始。那个演示只调用一次 addEventListener。
📊 票带记下什么
| 时刻 | 你读什么 | 纸条文字 | 容易混 |
|---|---|---|---|
| 监听器在票带上,点击 red | event.target.id |
red | 去读 currentTarget,那是 strip |
| 同一次点击,读 currentTarget | event.currentTarget.id |
strip | 去读票的 id |
| 同一个监听器,点击 blue | event.target.id |
blue | 给 blue 单独再挂监听器 |
| 监听器之后才创建 gold,再点击 | event.target.id |
gold | 以为新按钮没声音 |
| 点击票带本身,有守卫 | 纸条 | skip | 写成 id strip |
| 页面脚本 | 这一行 | red / blue / gold / 1 | 牌子那一行,或末尾是 3 |
点一个按钮,是同一个事件。沿路每个监听器看到的 target 都还是那个按钮。这里只有一个监听器,在票带上,所以函数运行时 currentTarget 是票带。点击能走到票带,是因为没有人调用 stopPropagation。牌子那一页把这段路切断了。这一页不切断。
preventDefault 用来取消浏览器的默认动作,比如链接离开本页。这里是按钮,不是链接。不写 preventDefault 就是这一页的全部,不是漏了一行。
如果 event.target 和 event.currentTarget 是同一个节点,点击落在票带上,不是落在按钮上。函数写成 skip,不把纸当成一张票。seen.join(" / ") 只连接已经推进 seen 的 id。hands 写在后面。所以最后一个记号是 addEventListener 调用了几次,不是点了几张票。
点击下面的那张票
监听器在票带上。函数把参数叫做 event,把 event.target.id 写进纸条。然后页面点击 red。它不在 red 上调用 addEventListener。
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
document.getElementById("red").click();
console.log(note.textContent);
输出是 red。监听器所在的元素是票带。target 不报告父元素。它报告点击落下去的那个按钮。
监听器属于谁
新的票带,同一个监听器,同样点击 red。纸条写下两个 id:先是 target,然后是 currentTarget。
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id + " " + event.currentTarget.id;
}
strip.addEventListener("click", take);
document.getElementById("red").click();
console.log(note.textContent);
输出是 red strip。第一个词是票。第二个词是拥有这个监听器的元素。如果监听器就在 red 自己身上,再点击 red,输出会是 red red。这次读的是票带,所以两个 id 不一样。
一开始就在旁边的票
再来一条新票带。还是一个监听器,只在票带上。这次点的是 blue。blue 从一开始就在标记里。它自己没有监听器。
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
document.getElementById("blue").click();
console.log(note.textContent);
输出是 blue。一次 addEventListener 盖住了票带里原本就有的两个按钮。blue 没有换掉 red。red 也不需要第二次调用。
后印出来的票
先把监听器存好。然后页面造一个按钮,把 id 设成 gold,把 type 设成 button,把文字设成 gold,再接到票带上。这之后才点击 gold。
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
let gold = document.createElement("button");
gold.id = "gold";
gold.type = "button";
gold.textContent = "gold";
strip.appendChild(gold);
gold.click();
console.log(note.textContent);
输出是 gold。addEventListener 运行的时候,gold 还不存在。点击从 gold 往上走到票带,target 是 gold。你不必给 gold 再挂监听器。
点在纸上
票带本身也是可以点击的节点。如果点击的 target 就是票带,就没有票的 id 可以留下。函数比较 target 和 currentTarget。两者相同,纸条就写成 skip。
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
if (event.target === event.currentTarget) {
note.textContent = "skip";
return;
}
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
strip.click();
console.log(note.textContent);
输出是 skip。target 和 currentTarget 都是票带,守卫因此拒绝这张纸。点 red 仍然会写成 red,因为那两个引用并不相同。拿掉守卫,strip.click() 就会把 id strip 写进纸条。
票带下面的这一行
脚本在 body 末尾,票带和段落都已经在。存下一个监听器,hands 变成 1。先点 red,再点 blue。每张票的 id 推进 seen。gold 被创建、接上、点击,所以 gold 也进入 seen。这一行写在第三次点击之后。页面从不点击票带本身,所以 skip 不在这一行里。
<!DOCTYPE html>
<html>
<head>
<title>event-delegation — page</title>
</head>
<body>
<div id="strip">
<button id="red" type="button">red</button>
<button id="blue" type="button">blue</button>
</div>
<p id="note">blank</p>
<p id="line">…</p>
<script>
let strip = document.getElementById("strip");
let note = document.getElementById("note");
let seen = [];
let hands = 0;
function take(event) {
if (event.target === event.currentTarget) {
note.textContent = "skip";
return;
}
note.textContent = event.target.id;
seen.push(event.target.id);
}
strip.addEventListener("click", take);
hands = hands + 1;
document.getElementById("red").click();
document.getElementById("blue").click();
let gold = document.createElement("button");
gold.id = "gold";
gold.type = "button";
gold.textContent = "gold";
strip.appendChild(gold);
gold.click();
document.getElementById("line").textContent =
seen.join(" / ") + " / " + hands;
</script>
</body>
</html>
这一行是 red / blue / gold / 1。三个 id 是按顺序被点击的票。最后一个记号是 hands。addEventListener 跑了一次,所以这个记号是 1,不是 3。纸条停在 gold,因为最后一次点击是 gold。blank 这个词消失了。skip 这个词不会出现。牌子那一行和衣帽钩那一行是另外的页面。
按钮里只有文字时,event.target 停在按钮上,id 就是 red、blue 或 gold。按钮里放一个 span,点击可能落在 span 上。span 没有 id。这一页不这么做。
在窗口前
- 在票带上放一个监听器,点击 red。纸条是 red。red 自己没有监听器。
- 用同一个监听器点击 blue。纸条是 blue。
- 创建 gold,接上,再点击。纸条是 gold。监听器早就在票带上。
- 点击票带本身。有守卫时,纸条是 skip。然后读这一行:red / blue / gold / 1。
窗口前容易错的地方
标签仍然是 red、blue 和 gold。纸条不是“票”这个词。
- red 上一个监听器、blue 上另一个,然后奇怪 gold 为什么没声音
- 想要票,却去读
event.currentTarget - 想要票带,却去读
event.target - 忘了守卫,于是
strip.click()写成 strip - 按钮里有
span,于是target是 span,id 是空的 - 在 gold 上再调用
addEventListener,然后把hands数成 2 - 调用
preventDefault或stopPropagation;那是牌子那一页 - 以为函数的参数就是按钮元素
- 把这一行读成 click / seal / plaque / true / quiet,或 wool silk / 2 / peg
- 因为点了三张票,就把最后一个记号读成 3
留下来的
- 票带上一次
addEventListener能听见里面每个按钮 - 点击 red 时,
event.target.id是 red,event.currentTarget.id是 strip - 同一个监听器在点击 blue 时写成 blue
- 监听器之后才创建的 gold,仍然写成 gold
- 点击票带本身,有守卫时,写成 skip
- 页面这一行是 red / blue / gold / 1
🧠 测试您的知识
测试您的知识
通过这个互动测验挑战自己,看看你对这个主题的理解程度如何
📝 说明
- 仔细阅读每个问题
- 为每个问题选择最佳答案
- 您可以随时重新参加测验
- 您的进度将显示在顶部