DOM 事件、事件委托和默认动作
事件捕获和冒泡
捕获和冒泡的历史问题
1 | <div class="grandfather"> |
IE 和 Netscape 都认为点击文字,也算点击了 son,father,grandfather。但在调用顺序上,IE5 认为先调用 fnGrandFather,
而 Netscape 认为先调用 fnSon。最后 W3C 在 2002 发布 DOM Level 2 Events Specification 文档规定:
- 浏览器应该同时支持两种调用顺序,开发者自己选择函数在捕获阶段执行还是冒泡阶段执行
- 首先按爷爷=>爸爸=>儿子的顺序看有没有函数监听(捕获阶段)
- 然后按儿子=>爸爸=>爷爷顺序看有没有函数监听(冒泡阶段)
- 有监听函数就调用,并提供事件信息(event对象),没有就跳过
捕获和冒泡阶段
1 | father.addEventListener('click',fn,bool) // bool 不传或为 falsy 就会在冒泡阶段调用 fn |
一种特殊情况
- 只有一个 div 被监听
- 分别在捕获阶段和冒泡阶段监听 click 事件
- 用户点击的元素就是开发者监听的
在这种情况下谁先监听谁先执行,并不一定是在捕获阶段监听的函数先执行。
取消冒泡
1 | e.stopPropagation() //中断冒泡,浏览器不再向上通知 |
target 和 currentTarget
e.target
是用户操作的元素e.currentTarget
是当前设置监听的元素,事件监听回调函数中的 this 指向的是 e.currentTarget
自定义事件
1 | button.addEventListener('click',()=>{ |
事件委托
应用场景
- 监听100个按钮的点击事件,监听这100个按钮的祖先,等冒泡的时候判断 target 是不是这100个按钮中的一个
- 监听目前还不存在的元素的点击事件,监听祖先,等点击的时候看是不是想要监听的元素
优点
- 省监听数(内存)
- 可以监听动态元素
手写事件委托
简写版:
1 | function on(eventType,element,selector,fn){ |
简写版存在一个问题,如果点击的是 selector 内部的元素,事件委托就会失效。因此需要递归判断 target 的父元素/爷爷元素等是否是 selector。完整版:
1 | function on(eventType,element,selector,fn){ |
JS 和 DOM 事件的关系
DOM 事件并不是 JS 的内容,事实上 DOM 只是浏览器提供的 web api。JS 只是调用了 DOM 提供的 addEventListener 而已。而 NodeJS 实现了类似的事件系统 EventEmitter,不过没有 DOM 事件中冒泡,捕获等行为。
默认动作
许多事件会自动触发浏览器执行某些行为。比如点击表单的提交按钮会触发提交表单到服务器的行为,点击链接触发导航到该 URL 的行为。
阻止浏览器默认动作
- 使用 event 对象的 preventDefault() 方法
- 如果处理程序是使用
on<event>
(而不是 addEventListener) 分配的,那么返回 false 也是有效的
1 | <a href="/" onclick="return false">Click here</a> |
form 表单的正确写法
之前遇到过一个问题,在 form 表单中点击按钮自动刷新了整个页面。原因是在 chrome 浏览器中,即使 button type 没有设置为 submit ,也会被视为提交按钮,点击后会触发表单的提交动作从而导致整个页面刷新。
1 | <template> |
通常我们不需要 form 的默认动作,可以监听 form 的 submit 事件阻止默认动作并调用我们自定义的 submit 函数。在 form 标签中的 input 元素中回车也可以触发submit 事件,点击 label 元素会将焦点放在内部的 input 元素上。
如何阻止滚动?
- 阻止 scroll 事件的默认动作是没用的,因为现有滚动才会有滚动事件
- 要阻止滚动可以阻止 wheel 和 touchstart(移动端) 的默认动作
- CSS 隐藏滚动条,避免用户拖动滚动条进行滚动
参考: