事件是某事发生的信号,所有的 DOM 节点都生成这样的信号(但事件不仅限于 DOM)
DOM 没有被 W3C 定为标准之前
DOM 成为 W3C 的标准后,称为 DOM Level 1
DOM Level 1由两个模块组成:
在 DOM Level 1 的基础上进行了扩展。为节点添加了更多方法和属性等
添加新的模块,包括:视图、事件、范围、遍历、样式等
进一步扩展了 DOM,增加了 XPath 模块、加载和保存(DOM Load and Save)模块等,开始支持 XML1.0 规范
DOM Level 0 事件
处理程序可以设置在 HTML 中名为 on<event> 的 attribute 中:
<!-- 直接写 JS 逻辑 -->
<input value="Click me" onclick="alert('Click!')" type="button">
<!-- 指向一个 JS 函数 -->
<input value="Click me" onclick="handleClick()" type="button">
<script>
function handleClick() {
alert('Click!')
}
</script>
如果一个处理程序是通过 HTML 特性(attribute)分配的,那么随后浏览器读取它,并从特性的内容创建一个新函数,并将这个函数写入 DOM 属性(property)
因此,这种方法实际上与前一种方法相同:
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you')
}
</script>
要移除一个处理程序 —— 赋值 elem.onclick = null
因为只有一个 onclick 属性,所以无法分配更多事件处理程序:
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
elem.onclick = function() { // 覆盖了现有的处理程序
alert('After')
}
</script>
处理程序只会在冒泡(+ 目标)阶段执行
this处理程序中的 this 指向对应的元素,就是处理程序所在的那个元素
attribute 需要加:
<input value="Click me" onclick="handleClick()" type="button">
<script>
function handleClick() {
alert('Click!')
}
</script>
当浏览器读取 HTML 特性(attribute)时,浏览器将会使用特性中的内容创建一个处理程序:
button.onclick = function() {
handleClick()
// 不加括号的话就变成了
handleClick
// 函数不会调用
}
property 不加:
<input value="Click me" type="button">
<script>
function handleClick() {
alert('Click!')
}
button.onclick = handleClick
// 加括号的话就变成了
button.onclick = handleClick()
// 实际上获得的是函数执行的结果,即 undefined(因为这个函数没有返回值),此代码不会工作
</script>
setAttribute这样的调用会失效:
// 点击 <body> 将产生 error,
// 因为特性总是字符串的,函数变成了一个字符串
document.body.setAttribute('onclick', function() { alert(1) })
<div onclick="...">、<div OnClick="..."> 都可以,推荐全小写elem.onclick,而不是 elem.ONCLICK,否则代码不会工作addEventListenerEventTarget.addEventListener(event: string, listener: function, options?)
EventTarget:事件目标,可能是:
elementdocumentwindowXMLHttpRequestevent:事件名,如 click,注意没有 on 且全小写listener:事件处理程序,回调函数或是一个包含 handleEvent 方法的对象options:可以是一个对象,也可以是 boolean
once: boolean:默认 false,如果为 true,会在被触发后自动删除监听器capture: boolean
false:默认值,冒泡阶段回调true:捕获阶段回调passive: boolean:默认 false
true 时,表示 listener 永远不会调用 preventDefault()listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告passive 设为 true 可以启用性能优化,并可大幅改善应用性能,因 preventDefault 的调用导致浏览器必须等待监听函数执行完成,造成阻塞passive|处理程序选项:passive]]signal:AbortSignal,该 AbortSignal 的 abort() 方法被调用时,监听器会被移除boolean:同 captureEventTarget.removeEventListener(event: string, listener: function, options?)
options:可以是一个对象,也可以是 boolean
{ capture: boolean }boolean:同 capturecapture: true)和冒泡(capture: false)的事件监听器互不相关,需要分别移除。移除捕获监听器不会影响非捕获版本的相同监听器,反之亦然。addEventListener() 的工作原理是将函数或对象添加到调用它的 EventTarget 上的指定事件类型的事件侦听器列表中
对于某些事件,只能通过 addEventListener 设置处理程序,如:
// 永远不会运行
document.onDOMContentLoaded = function() {
alert("DOM built")
}
// 正确方式
document.addEventListener("DOMContentLoaded", function() {
alert("DOM built")
});
listener 的触发阶段(捕获或冒泡)当事件发生时,会创建一个 event 对象,将详细信息放入其中(如:鼠标指针坐标、键盘按键信息),并将其作为参数传递给处理程序:
<input type="button" onclick="alert(event.type)" value="Event type">
<input type="button" onclick="foo(event)" value="Event type">
<script>
foo(e) {}
elem.onclick = function(e) {}
elem.addEventListener('click', function(e) {})
elem.addEventListener('click', { handleEvent(e) {} })
</script>
event 对象的一些属性:
type:事件类型,如 "click"event.currentTarget:EventTarget(如:绑定处理器的元素),这与 this 相同
this 被修改,就可以从 event.currentTarget 获取event.target:事件发生的目标(即:目标阶段)
event.currentTarget 是当前绑定处理器的目标;event.target 是事件发生的目标event.eventPhase:当前阶段,[[#事件传播流程]]
1:capturing2:target3:bubblingevent.cancelable:当前事件是否支持取消,[[#浏览器默认行为]]event.defaultPrevented:当前事件是否被阻止浏览器默认行为![[Pasted image 20240429151024.png|500]]
window 开始,自顶向下地捕获事件发生的目标元素<html>,然后再到 document ,有些事件甚至会到达 windowfocus、blur、scroll、wheel、mouseenter、mouseleave 等事件不会冒泡event.stopPropagation():阻止继续捕获或冒泡event.stopImmediatePropagation()
有时停止传播会产生隐藏的陷阱,产生“死区”
通常,没有真正的必要去阻止冒泡。一项看似需要阻止冒泡的任务,可以通过其他方法解决:
event.defaultPrevented 来代替,来通知其他事件处理程序,该事件已经被处理event 对象,并在另一个处理程序中读取该数据,这样就可以向父处理程序传递有关下层处理程序的信息如果有许多以类似方式处理的元素,就不必为每个元素分配一个处理程序,而是将单个处理程序放在它们的共同祖先上
在处理程序中,通过 event.target 以查看事件实际发生的位置并进行处理
好处:
局限性:
event.stopPropagation()),而有些事件不会冒泡许多事件会自动触发浏览器执行某些行为。例如:
使用 JS 处理一个事件,通常不希望发生相应的浏览器行为,而是想要实现其他行为进行替代。
有两种方式来告诉浏览器不希望它执行默认行为:
event 对象的 event.preventDefault() 方法on<event>(而不是 addEventListener)分配的,那返回 false 也同样有效<a href="https://www.baidu.com/" onclick="event.preventDefault()">此时点击不会跳转</a>
<a href="https://www.baidu.com/" onclick="return false">此时点击不会跳转</a>
<a href="https://www.baidu.com/" id="linkDemo">此时点击不会跳转</a>
<script>
linkDemo.onclick = function() { return false }
</script>
但是注意:
<a href="https://www.baidu.com/" onclick="demo()">没有阻止浏览器默认行为</a>
<script>
function demo() { return false }
</script>
这样并没有阻止浏览器默认行为,因为上面代码相当于:
function demo() { return false }
a.onclick = function() { demo() }
需要这样写:
<a href="https://www.baidu.com/" onclick="return demo()">此时点击不会跳转</a>
<script>
function demo() { return false }
</script>
某些事件会相互转化。如果阻止了第一个事件,那就没有第二个事件了
如:阻止了鼠标按下(mousedown)的浏览器的默认事件,会导致 input 不能获取到焦点(focus)
这是因为浏览器行为在 mousedown 上被取消。如果用另一种方式进行输入,则仍然可以进行聚焦。如使用 Tab 键从第一个输入切换到第二个输入,但鼠标点击则不行。
对于不可取消的事件(如:通过 EventTarget.dispatchEvent() 分派的且没有指定 cancelable: true 的事件),调用 preventDefault() 将没有任何效果
可以使用 event.cancelable 来检查该事件是否支持取消
passiveaddEventListener 的可选项 passive: true 向浏览器发出信号,表明处理程序将不会调用 preventDefault()
为什么需要这样做?
移动设备上会发生一些事件,例如 touchmove(当用户在屏幕上移动手指时),默认情况下会导致滚动,但是可以使用处理程序的 preventDefault() 来阻止滚动
因此,当浏览器检测到此类事件时,它必须首先处理所有处理程序,然后如果没有任何地方调用 preventDefault,则页面可以继续滚动。但这可能会导致 UI 中不必要的延迟和“抖动”。
passive: true 选项告诉浏览器,处理程序不会取消滚动。然后浏览器立即滚动页面以提供最大程度的流畅体验,并通过某种方式处理事件
对于某些浏览器(Firefox,Chrome),默认情况下,touchstart 和 touchmove 事件的 passive 为 true
一个历史页面,上面有若干按钮的点击逻辑,每个按钮都有自己的 click 事件
新需求来了:给用户信息添加了一个属性 banned = true,此用户点击页面上的任何按钮或者元素,都不可响应原来的函数。而是直接 alert 提示:你被封禁了。
window 添加捕获阶段的点击处理逻辑,阻止事件传播window.addEventListener('click', event => {
if (user.banned) {
alert('你被封禁了')
event.stopProgagtion()
}
}, true)