正在加载...

事件委托

售票窗口有一条票带。父元素是 #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。

读出这张票

javascript
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。

读出两个 id

javascript
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 从一开始就在标记里。它自己没有监听器。

听见旁边那张票

javascript
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。

听见后加的票

javascript
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。

放过点在票带上的点击

javascript
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 不在这一行里。

页面演示 — 票带

html
<!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。这一页不这么做。

在窗口前

  1. 在票带上放一个监听器,点击 red。纸条是 red。red 自己没有监听器。
  2. 用同一个监听器点击 blue。纸条是 blue。
  3. 创建 gold,接上,再点击。纸条是 gold。监听器早就在票带上。
  4. 点击票带本身。有守卫时,纸条是 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

🧠 测试您的知识

准备开始

测试您的知识

通过这个互动测验挑战自己,看看你对这个主题的理解程度如何

❓
8
问题
🎯
70%
及格要求
♾️
∞
时间
🔄
∞
尝试次数

📝 说明

  • 仔细阅读每个问题
  • 为每个问题选择最佳答案
  • 您可以随时重新参加测验
  • 您的进度将显示在顶部