Skip to content

Event 对象与DOM 对象深入分析(Day16 2/3) ​

本页对应 Day16_DOM,共 6 段视频,约 1 小时 22 分。

学习目标 ​

  • 能列举超链接跳转、表单提交/重置、右键菜单、滚轮滚动等浏览器默认行为,并使用 event.preventDefault() 在对应事件回调中阻止指定默认行为。
  • 能说明 return false 阻止默认行为的适用条件,并判断在 onclick 与 addEventListener 两种绑定方式下是否生效。
  • 能使用事件委托为动态新增元素绑定交互:将事件监听在稳定的祖先元素上,通过 event.target 及类名/标签名判断目标元素并执行相应操作。
  • 能描述元素对象与事件对象原型链的关键节点和继承关系,并识别调试工具显示结果与真实构造函数之间的差异。
  • 能区分 HTMLCollection 与 NodeList 的成员类型、forEach 支持情况及动态/静态集合差异,并能解释为何 HTMLCollection 动态更新后仍需要事件委托。

知识点 ​

先保留本节的事件清单 ​

1. 鼠标事件
   click
   dblclick
   contextmenu
   mousedown
   mouseup
   mousemove
   mouseenter mouseover
   mouseleave mouseout
   mousewheel DOMMouseScroll

2. 键盘事件
   keydown
   keyup
   keypress

3. 文档事件
   load
   DOMContentLoaded

4. 表单事件
   submit
   reset
   focus
   blur
   select
   input
   change

5. 图片事件
   load
   error

6. 过渡事件
   transitionstart
   transitionend
   transitionrun

7. 动画事件
   animationstart
   animationend
   animationiteration

8. 其他事件
   scroll
   resize

回顾事件不能只背名字,要按“谁触发、什么时候触发、有没有默认行为、能不能冒泡委托”去串。

鼠标事件:按下、松开、移动、进入离开要分清 ​

为什么需要:鼠标交互最常用,点击、右键菜单、滚轮滚动都靠它。怎么想:先区分“点一下”其实是 mousedown→mouseup→click;进入离开要区分是否冒泡。怎么做:click 做点击,contextmenu 做右键,mousemove 做移动,mouseenter/mouseleave 不冒泡,mouseover/mouseout 会冒泡。易错点:contextmenu 的默认行为是弹系统菜单,可用 event.preventDefault() 阻止;滚轮事件在旧浏览器有 DOMMouseScroll 兼容问题,且部分滚轮默认行为浏览器不允许阻止。

键盘事件:keydown、keyup 与 keypress ​

为什么需要:表单回车提交、快捷键都依赖键盘事件。怎么想:keydown 是按下,keyup 是松开,keypress 更偏向字符输入。怎么做:判断按键优先看 keydown/keyup 的事件对象;表单回车提交最终会触发 submit。易错点:不要用 keypress 判断所有键,它已不推荐;阻止默认行为要在对应事件回调里做。

文档事件:load 与 DOMContentLoaded ​

为什么需要:脚本执行时 DOM 可能还没解析完。怎么想:DOMContentLoaded 是 DOM 解析完成;load 还要等图片等资源。怎么做:要操作 DOM 优先等 DOMContentLoaded;要等全部资源再等 load。易错点:脚本放在 head 里直接查元素容易拿不到,不是选择器写错,而是执行时机太早。

表单事件:submit、reset、focus、blur、select、input、change ​

为什么需要:表单验证、实时提示、提交拦截都靠这些事件。怎么想:submit 监听表单,不是只监听提交按钮;input 是输入就触发,change 常在值改变并失焦后触发。怎么做:验证不通过时在 submit 回调里 event.preventDefault()。易错点:转写稿调试过:点提交按钮和按回车都会触发表单提交;阻止后两者都提交不了。return false 只在 on 方式下可阻止默认行为,addEventListener 下要用 preventDefault。

图片、过渡、动画与其他事件 ​

为什么需要:资源加载失败、CSS 过渡/动画状态、页面滚动和窗口变化都要监听。怎么想:图片看 load/error;过渡看 transitionstart/end/run;动画看 animationstart/end/iteration;滚动和尺寸看 scroll/resize。怎么做:img 上监听 error 做兜底;过渡动画事件要先有对应 CSS。易错点:没有过渡/动画就不会触发对应事件;scroll 触发频繁,先确认监听 window 还是滚动容器。

重点回顾一:事件回调先于默认行为 ​

为什么需要:超链接跳转、表单提交、右键菜单、滚轮滚动都是浏览器天生默认行为。怎么想:先明确“哪个动作产生默认行为”,再监听同动作事件,在回调里阻止。怎么做:event.preventDefault()。转写稿调试:点超链接先弹框,点确定后原本会跳转;加上 preventDefault 后不跳。表单里点搜索或按回车都会提交,阻止后都不提交。右键监听 document 可让整页系统菜单消失。易错点:滚轮默认行为有些浏览器不允许阻止;return false 不是标准写法,addEventListener 下无效。

重点回顾二:事件委托解决新增元素没有事件 ​

为什么需要:转写稿里给已有删除按钮循环绑定 click,新增 li 里的 span 没有监听,所以点新删除按钮没反应。怎么想:新增元素不在旧集合的循环里;把事件监听给始终存在的祖先 UL,利用冒泡,再判断 event.target。怎么做:ul.onclick = function(e){ if(e.target.className === 'delete'){ e.target.parentElement.remove(); } }。易错点:HTMLCollection 会动态更新,但 for 循环只在页面打开时执行一次,不会因为集合变长再跑一遍,所以仍需要事件委托;判断目标元素可用类名或标签名。

阻止浏览器默认行为:先有事件回调,后有默认动作 ​

为什么需要:超链接会跳转、表单会提交、右键会弹系统菜单、滚轮会滚动页面,这些都是浏览器“天生自带”的默认行为。做表单验证、禁止跳转、自定义右键菜单时,需要把它们拦下来。

怎么想:默认行为不是我们写的,但它一定发生在事件回调之后;也就是说,先执行我们绑定的事件处理函数,再轮到浏览器默认动作。因此要在对应事件的回调里阻止。

怎么做:用事件对象的 preventDefault()。例如阻止超链接跳转、表单提交:

js
a.onclick = function (e) {
  e.preventDefault(); // 阻止跳转
};

form.onsubmit = function (e) {
  e.preventDefault(); // 阻止提交
};

阻止右键菜单可监听 contextmenu;若要全文档生效,就监听到 document 上。滚轮事件里调用 preventDefault() 在 Chrome 中通常阻止不了页面滚动,浏览器会提示该默认行为不允许被代码阻止。

易错点:return false 只有在 onclick = function(){} 这类方式下才可阻止默认行为;用 addEventListener 时必须老老实实用 preventDefault()。网上常说“return false 既能阻止默认行为又能阻止冒泡”,很多是受 jQuery 写法影响,原生里不要混用。阻止冒泡仍然只有 stopPropagation() 一类方式。

事件委托:把监听交给祖先,再判断目标元素 ​

为什么需要:给一批删除按钮逐个绑定点击事件,只能覆盖“绑定那一刻已经存在”的元素;后添加的列表项没有绑定,就删不掉。若元素数量很大,逐个绑定也浪费内存。

怎么想:点击子元素时事件会冒泡到祖先。把点击事件委托给始终存在的 ul,在回调里判断 event.target 是不是删除按钮;如果是,再通过 parentElement 找到它所在的 li 删除。

怎么做:核心代码如下:

js
ul.onclick = function (e) {
  if (e.target.className === "delete") {
    ul.removeChild(e.target.parentElement);
  }
};

新增 li 时也要补上删除按钮结构,之后新元素同样能被删除,因为事件监听在 ul 上,新 li 的点击会冒泡上去。

易错点:不能直接写 this 当被点击元素;回调里的 this 是绑定监听的那个祖先。要判断 e.target,并用 parentElement 找父级。委托对象要选打开页面就存在、且能包住目标元素的祖先,document 也可以,但通常选更近的容器更清晰。

元素对象与事件对象的原型链:了解层次即可 ​

为什么需要:理解 div、p 都是元素,鼠标事件、键盘事件都是事件对象,能解释“不同类型既有特性又有共性”。

怎么想:元素对象原型链大致为:HTMLDivElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → EventTarget.prototype → Object.prototype。p 元素构造是 HTMLParagraphElement,到 HTMLElement 之后与 div 相交。鼠标事件对象大致为:MouseEvent.prototype → UIEvent.prototype → Event.prototype → Object.prototype。

怎么做:可用 constructor、prototype 观察,但只需了解。调试工具会把对象显示得像构造函数,只有前面带 F 的才是函数;不要被显示样子带偏。

易错点:原型链第一层的 constructor 较准,后面对象是给别人当原型的,不能只看显示结果判断构造函数。

HTMLCollection 与 NodeList:动态和静态是关键 ​

为什么需要:getElementsByTagName、getElementsByClassName、children 得到 HTMLCollection;querySelectorAll 得到 NodeList。二者都有索引、像数组,但行为不同。

怎么想:HTMLCollection 是动态集合,获取后新增符合条件的元素会自动进入集合;querySelectorAll 得到的 NodeList 是静态集合,只保留获取那一刻的结果。NodeList 通常有 forEach,HTMLCollection 没有,可借数组方法或写 for 循环。

易错点:动态集合会更新,不代表你之前写的循环会重新执行。

HTMLCollection 仍然需要事件委托 ​

有人会想:HTMLCollection 会动态更新,是不是新增删除按钮就不用委托了?验证后发现不行。因为循环绑定只在页面打开时执行一次,当时集合里只有已有按钮;后来集合虽然更新,循环没有第二次执行,新按钮仍没绑定事件。所以要让新增元素也有事件,仍应使用事件委托。

阻止浏览器默认行为:先理解“默认”,再谈“阻止” ​

为什么需要:有些元素不写 JS 也有动作:点超链接会跳转、提交按钮或回车会提交表单、重置按钮会重置、右键会弹系统菜单、滚轮会让页面滚动。这些是浏览器天生自带的行为,叫默认行为。
怎么想:要阻止默认行为,先明确“哪个动作触发它”,再给同一动作绑定事件,在回调里阻止。
怎么做:标准写法是 event.preventDefault()。

js
const a = document.querySelector('a');
a.addEventListener('click', function (event) {
  alert('超链接被点击了');
  event.preventDefault(); // 阻止跳转
});

表单提交同理:

js
const form = document.querySelector('form');
form.addEventListener('submit', function (event) {
  console.log('正在被提交');
  event.preventDefault(); // 表单不再提交
});

右键菜单可绑在某个元素上,也可绑到 document 上阻止整页右键菜单:

js
document.addEventListener('contextmenu', function (event) {
  event.preventDefault();
});

转写中调试发现:事件回调会先执行,默认行为靠后;右键默认行为在冒泡结束之后才发生,所以监听在 document 上也能拦住。
易错点:return false 只在 onclick = function(){} 这类方式中可阻止默认行为;用 addEventListener 时不要依赖它,老老实实用 event.preventDefault()。滚轮的 wheel 事件里调用 preventDefault() 在 Chrome 中阻止不了页面滚动,浏览器不允许开发者随便阻止这类默认行为。

本节笔记未附图片;以下保留转写中的关键代码思路。

事件委托:把事件交给祖先,再判断目标 ​

为什么需要:给已存在的删除按钮绑定点击后,新增的 li 里也有删除按钮,但点它没反应。原因是代码在新增前只给已有按钮绑定了事件。
怎么想:事件会冒泡。把点击事件委托给一直存在的祖先 ul,在回调里判断 event.target 是不是删除按钮。
怎么做:

js
const ul = document.querySelector('#todo-content');
ul.addEventListener('click', function (event) {
  if (event.target.className === 'delete') {
    ul.removeChild(event.target.parentElement);
  }
});

新增 li 时补上删除按钮:

js
const li = document.createElement('li');
li.innerHTML = '吃午饭 <span class="delete">×</span>';
ul.appendChild(li);

易错点:不能写 this.parentElement 删除,因为委托回调里的 this 是 ul;要用 event.target.parentElement。判断目标可用类名、tagName 或 nodeName。事件委托还能减少大量元素逐个绑定事件的内存和循环开销。

元素对象原型链:知道很长、会继承即可 ​

为什么需要:理解不同元素为什么既有特性又有共性。
怎么想:div 元素原型链大致为:HTMLDivElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → EventTarget.prototype → Object.prototype;p 元素从 HTMLParagraphElement.prototype 进入,后面与 div 相交。
怎么做:可输出 div.constructor、HTMLDivElement.prototype 观察,但不必深究。
易错点:不要被调试工具显示带偏;只有前面带 F 的才是构造函数,显示成 HTMLDivElement 样子的也可能是对象,不是构造函数。

事件对象原型链:不同类型事件有相交点 ​

为什么需要:鼠标事件、键盘事件既有各自特性,也有事件共性。
怎么想:鼠标事件对象大致从 MouseEvent.prototype → UIEvent.prototype → Event.prototype → Object.prototype。
怎么做:监听 mousedown 后输出 event.constructor 观察即可。
易错点:同样不要只看调试工具显示;知道不同类型事件在原型链上有相交点即可。

HTMLCollection 与 NodeList:动态和静态要分清 ​

为什么需要:不同获取方式得到集合类型不同,行为不同。
怎么想:getElementsByTagName、getElementsByClassName、children 得到 HTMLCollection;querySelectorAll 得到 NodeList。NodeList 有 forEach,HTMLCollection 没有;HTMLCollection 成员是元素,NodeList 可含其他节点。
怎么做:

js
const ul = document.querySelector('#books');
const lis1 = ul.getElementsByTagName('li'); // HTMLCollection,动态
const lis2 = ul.querySelectorAll('li');     // NodeList,静态

新增 li 后,lis1.length 会自动变多,lis2.length 不变。
易错点:HTMLCollection 动态更新,不代表新增元素自动有事件。转写中给删除按钮用 getElementsByTagName 得到集合后 for 循环绑定事件,新增后集合长度从 6 变 7,但循环只在页面打开时执行了一次,新按钮仍没绑上事件。所以 HTMLCollection 仍然需要事件委托。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例1:阻止浏览器默认行为 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>阻止浏览器默认行为</title>
  <style>
    #box {
      width: 600px;
      height: 250px;
      background: #ccc;
    }
    #scroll-box {
      height: 1200px;
    }
  </style>
</head>
<body>
  <a id="aBox" href="https://www.atguigu.com">上硅谷</a>

  <form id="formBox" action="https://www.baidu.com/s" method="get">
    <input type="text" name="wd">
    <button type="submit">搜索</button>
  </form>

  <div id="box">在盒子上右键试试</div>
  <div id="scroll-box">滚动区域</div>

  <script>
    var aBox = document.querySelector('#aBox');
    aBox.onclick = function (event) {
      alert('超链接被点击了');
      event.preventDefault();
    };

    var formBox = document.querySelector('#formBox');
    formBox.onsubmit = function (event) {
      console.log('正在被提交');
      event.preventDefault();
    };

    document.oncontextmenu = function (event) {
      event.preventDefault();
    };

    window.onmousewheel = function (event) {
      // 转写稿中演示:滚轮默认行为阻止不了
      event.preventDefault();
    };
  </script>
</body>
</html>

要点:

  • event.preventDefault() 是阻止默认行为的标准写法;超链接不跳转、表单不提交、右键不出系统菜单都靠它。
  • 事件回调函数先执行,浏览器默认行为靠后执行;所以在回调里阻止默认行为才有效。
  • return false 只在 onclick = function(){} 这类方式下可阻止默认行为;addEventListener 中必须用 event.preventDefault()。
  • 滚轮默认行为在转写稿中演示为“阻止不了”,说明有些默认行为浏览器不允许代码阻止。
  • 表单阻止默认行为常用于表单验证:验证不通过时不提交。

示例2:事件委托实现新增元素也能删除 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>事件委托</title>
  <style>
    #todo-content li {
      position: relative;
      padding: 10px;
      border-bottom: 1px solid #ddd;
    }
    #todo-content .delete {
      position: absolute;
      right: 10px;
      top: 10px;
      width: 16px;
      height: 16px;
      cursor: pointer;
      color: red;
    }
  </style>
</head>
<body>
  <ul id="todo-content">
    <li>做足疗<span class="delete">×</span></li>
    <li>钓鱼<span class="delete">×</span></li>
    <li>和雪丽去吃晚饭<span class="delete">×</span></li>
  </ul>
  <button id="addBtn">添加</button>

  <script>
    var todoContent = document.querySelector('#todo-content');
    var addBtn = document.querySelector('#addBtn');

    addBtn.onclick = function () {
      var li = document.createElement('li');
      li.innerHTML = '我要先和雪丽去吃午饭<span class="delete">×</span>';
      todoContent.appendChild(li);
    };

    todoContent.onclick = function (event) {
      if (event.target.className === 'delete') {
        todoContent.removeChild(event.target.parentElement);
      }
    };
  </script>
</body>
</html>

要点:

  • 事件委托把 click 绑定到始终存在的祖先元素 ul#todo-content 上,利用事件冒泡处理子元素点击。
  • event.target 是实际点击的目标元素;只有点击到 class="delete" 的 span 时才执行删除。
  • 删除的是 event.target.parentElement,即 span 所在的 li,不是 span 本身。
  • 新增的 li 中同样带 <span class="delete">×</span>,因为事件委托给 ul,所以新增元素也能删除。
  • 易错点:不要给每个 span 单独绑定事件,否则新增元素没有绑定,点击无效;回调中 this 是 ul,不是被点击的 span。

示例3:HTMLCollection 动态更新仍然需要事件委托 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>HTMLCollection与事件委托</title>
  <style>
    #books li {
      position: relative;
      padding: 10px;
      border-bottom: 1px solid #ddd;
    }
    #books .delete {
      position: absolute;
      right: 10px;
      top: 10px;
      cursor: pointer;
      color: red;
    }
  </style>
</head>
<body>
  <ul id="books">
    <li>1<span class="delete">×</span></li>
    <li>2<span class="delete">×</span></li>
    <li>3<span class="delete">×</span></li>
    <li>4<span class="delete">×</span></li>
    <li>5<span class="delete">×</span></li>
  </ul>
  <button id="addBtn">添加</button>

  <script>
    var ul = document.querySelector('#books');
    var deleteBtns = ul.getElementsByTagName('span'); // HTMLCollection:动态集合
    var liList = ul.querySelectorAll('li');          // NodeList:静态集合

    console.log('初始删除按钮:', deleteBtns.length); // 5
    console.log('初始liList:', liList.length);       // 5

    for (var i = 0; i < deleteBtns.length; i++) {
      deleteBtns[i].onclick = function (event) {
        ul.removeChild(event.target.parentElement);
      };
    }

    document.querySelector('#addBtn').onclick = function () {
      var li = document.createElement('li');
      li.innerHTML = '6<span class="delete">×</span>';
      ul.appendChild(li);

      console.log('添加后删除按钮:', deleteBtns.length); // 6,HTMLCollection 动态更新
      console.log('添加后liList:', liList.length);       // 5,NodeList 不更新

      // 但新增的 span 没有绑定 click,因为上面的 for 循环只执行了一次
    };
  </script>
</body>
</html>

要点:

  • getElementsByTagName 返回 HTMLCollection,是动态集合;querySelectorAll 返回 NodeList,是静态集合。
  • 添加新 li 后,deleteBtns.length 会变成 6,说明 HTMLCollection 会自动更新;liList.length 仍是 5。
  • 但 for 循环只在页面打开时执行一次,新增的 span 没有绑定 click,所以点击新增的删除按钮无效。
  • 因此即使 HTMLCollection 是动态的,仍然需要事件委托:把事件绑定到 ul 上,判断 event.target.className === 'delete'。
  • 易错点:不要把“集合动态更新”误认为“循环会重新执行”;新增元素有事件必须靠事件委托或重新绑定。

常见错误 ​

1. 用 addEventListener 绑事件时写 return false 想阻止默认行为

  • 现象:表单照样提交、链接照样跳转,控制台无报错,代码看起来“写了”。
  • 原因:return false 只有在 onclick = function(){} 这类赋值式绑定里才等效于阻止默认行为;addEventListener 的回调返回值会被直接丢弃,浏览器根本不看。
  • 修法:统一改用 e.preventDefault();不要依赖 return false,它既不可靠又容易让读代码的人误以为在阻止冒泡。

2. 事件委托里用 e.target 判断后,直接拿 e.target 去做删除

  • 现象:点叉号有时能删、有时没反应;点叉号里的文字或图标就失效。
  • 原因:e.target 是实际触发事件的最深层节点,可能是 span 内部的文本/子元素;同时 removeChild 需要的是被删元素本身,而 e.target 往往只是它的一部分。
  • 修法:判断条件放宽(classList.contains、closest('.delete')),再用 目标元素.closest('li') 或 parentElement 定位到真正要删的行;不要写死“父元素就是 li”。

3. 用 tagName 做判断时大小写不匹配

  • 现象:if (e.target.tagName === 'span') 永远为 false,委托逻辑整段不执行。
  • 原因:tagName / nodeName 返回的是大写形式 SPAN。
  • 修法:toLowerCase() 比较,或干脆用 classList.contains() 判断,类名不受大小写显示影响。

4. 循环里用 var 声明索引,回调中引用该索引

  • 现象:点任意一个删除按钮,删掉的都是最后一条;或者直接报错、删错元素。
  • 原因:var 没有块级作用域,回调执行时循环早已结束,索引停留在终值。
  • 修法:改用 let;更推荐在回调里用 this / e.currentTarget 指代被监听元素,彻底不依赖外部索引。

5. 以为 HTMLCollection 是动态集合,新增元素就自动带上事件

  • 现象:新增的列表项有删除按钮,但点了没反应;length 明明变了。
  • 原因:动态集合只保证“成员实时更新”,绑定事件的那次循环只在页面打开时执行过一次,新增元素从未进入过绑定流程。
  • 修法:该用事件委托还是用委托,把监听挂到始终存在的祖先上;动态集合不是事件自动化的替代品。

6. 想阻止滚轮/滚动类默认行为,preventDefault() 无效

  • 现象:e.preventDefault() 写了,页面照样滚,控制台还可能出现 “Unable to preventDefault inside passive event listener” 提示。
  • 原因:一是浏览器对 wheel、touchstart 等事件的监听器默认按 passive 处理,禁止在其中阻止默认行为;二是 scroll 这类事件的默认行为本身就不允许被取消。
  • 修法:addEventListener('wheel', fn, { passive: false });同时先确认该默认行为是否允许被取消,别在不可取消的事件上白费力气。

练习 ​

1. 委托改造题 给定一个 ul#todo,其中的 li 由 JS 动态创建,每项后面带一个 <span class="del">×</span>。要求:点击 × 删除所在行,点击 li 其他区域不删除;并且新增的行同样可删。写出完整代码,并用一句话说明为什么这里必须用委托而不是遍历绑定。

2. 表单验证拦截 一个搜索表单,要求输入内容为空时不允许提交(点按钮和按回车都不行)。分别用两种写法实现:onsubmit 中 return false、addEventListener 中 preventDefault。写完后回答:如果把这两种写法互换到对方的绑定方式上,会发生什么?

3. 局部禁用右键 要求整个页面右键不弹出系统菜单,但 textarea 区域内右键保持系统菜单可用。请用委托思路实现,并说明判断依据该选什么(tagName?closest?),以及为什么不能在 document 上无脑 preventDefault 就完事。

4. 动态集合观察实验 用 getElementsByClassName('del') 和 querySelectorAll('.del') 各拿一个集合,打印 length;然后用 JS 新增两个 .del 元素,再打印两个集合的 length。记录结果,并解释:既然其中一个会自动更新,为什么第 1 题的委托方案仍然不能被替代?

5. 原型链定位 分别写出 document.querySelector('div') 和 document.createTextNode('x') 两个对象的原型链上能找到的第一个相交节点,并说明这个相交节点意味着两者共享了哪些能力。

原视频对照 ​

#视频时长
1阻止浏览器默认行为19:31
2事件委托30:28
3元素对象的原型链14:30
4事件对象原型链03:16
5HTMLCollection和NodeList09:14
6HTMLCollection仍然需要事件委托05:17

小结 ​

  1. 阻止默认行为的唯一稳妥写法是 e.preventDefault();return false 只对赋值式 on 绑定有效,属于历史包袱,新代码不要用。
  2. 事件回调一定先于浏览器默认行为执行——这是“先拦截、后默认”能成立的前提;默认行为排在冒泡之后,所以挂在祖先上的处理器照样能拦住后代元素的默认行为。
  3. 并非所有默认行为都可被取消(滚轮、滚动等),遇到拦不住的情况先查规范,不要怀疑自己的写法。
  4. 事件委托的两个收益是独立的:一是覆盖动态新增元素,二是减少监听器数量。前者是功能刚需,后者是性能优化,判断要不要用委托时要把这两件事分开想。
  5. 委托的固定套路是“挂在稳定的祖先上 + 在回调里判断 e.target 是否命中目标”;判断依据优先用类名而非标签名,避免大小写和语义变化带来的脆弱性。
  6. e.target(真正触发的元素)和 e.currentTarget / this(监听器所在元素)不是一回事,混用是委托代码出错的第一大来源。
  7. HTMLCollection 的动态性只作用于“集合成员”,不作用于“已执行的绑定逻辑”;把它当成“事件会自动跟上”是典型误解。
  8. 元素对象的原型链再长也不必记,只需知道:不同元素、不同事件对象都在原型链上共享祖先,共性是继承来的,差异是自己那几层加的——调试工具显示的样子不代表真实构造函数,别被它带偏。