企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # 事件流 ![](https://box.kancloud.cn/7939ce4e01e61c6446a123734800d250_520x560.png =400x) 事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段 # 事件处理程序 click、mousemove、load 等都是事件的名字,响应某个事件的函数就叫事件处理程序(或事件侦听器),事件处理程序以 "on" 开头,因此,click 事件的事件处理程序就是 onclick,为事件指定事件处理程序的方式有多种。 ## 1、通过 HTML 指定事件处理程序 ```html <input type="button" value="Click Me" onclick="alert('Clicked')" /> ``` ```html <script type="text/javascript"> function showMessage(){ alert("Hello world!"); } </script> <input type="button" value="Click Me" onclick="showMessage()" /> ``` 这样指定事件处理程序有一些独到之处: - 函数中有一个局部变量 event,即事件对象 - 在这个函数内部,this 值等于事件的目标元素,例如: ```html <!-- 输出 "Click Me" --> <input type="button" value="Click Me" onclick="alert(this.value)"> ``` 缺点: - HTML 与 JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码 ## 2、DOM0 级事件处理程序 每个元素(包括 window 和 document)都有自己的事件处理程序属性,这些属性通常全部小写, 例如 onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,如下所示: ``` var btn = document.getElementById("myBtn") btn.onclick = function () { alert("Clicked") } ``` - 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理 - 使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,this 指向的是被设置事件处理程序的元素 - 可以通过 `btn.onclick = null` 的形式删除以该方法设置的事件处理程序 ## 3、DOM2 级事件处理程序 "DOM2级事件" 定义了两个方法,用于处理指定和删除事件处理程序的操作:`addEventListener()`和 `removeEventListener()`。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序 ``` var btn = document.getElementById("myBtn") btn.addEventListener("click", function () { alert(this.id) // 同样的,该函数被视为元素节点的方法 }, false) ``` - 使用 DOM2 级方法可以添加多个事件处理程序,按照添加顺序触发 - 通过`addEventListener()`添加的事件处理程序只能使用 `removeEventListener()`来移除,需要注意的是,使用匿名函数是无法删除的 ``` var btn = document.getElementById("myBtn"); btn.addEventListener("click", function(){ alert(this.id); }, false); //这里省略了其他代码 btn.removeEventListener("click", function(){ //没有用! alert(this.id); }, false); ``` 传入的第二个参数必须是同一个函数,函数是引用类型,即所指内存地址相同 ``` var btn = document.getElementById("myBtn"); var handler = function(){ alert(this.id); }; btn.addEventListener("click", handler, false); //这里省略了其他代码 btn.removeEventListener("click", handler, false); //有效! ``` - 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大程度地兼容各种浏览器 ## 4、兼容 IE 早期版本 IE 实现了与 DOM 中类似的两个方法:`attachEvent()`和`detachEvent()`。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于 IE8 及更早版本只支持事件冒泡,所以通过`attachEvent()`添加的事件处理程序都会被添加到冒泡阶段 # 事件对象 event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。下面列出**常用的**所有事件的 event 对象都具有的成员和方法。 |属性/方法 |类型 |说明| | -- | -- | -- | |bubbles |Boolean |表明事件是否冒泡| |cancelable| Boolean | 表明是否可以取消事件的默认行为| |currentTarget| Element |指向注册该事件处理程序的元素| |target |Element| 指向触发该事件的元素| |defaultPrevented| Boolean | 为 true 表示已经调用了 preventDefault()| |eventPhase |Integer |调用事件处理程序的阶段:1 表示捕获阶段,2 表示"处于目标",3 表示冒泡阶段| |preventDefault() |Function |取消事件的默认行为。如果 cancelable 是 true,则可以使用这个方法 |stopImmediatePropagation()| Function | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用| |stopPropagation() |Function | 取消事件的进一步捕获或冒泡。如果 bubbles 为 true,则可以使用这个方法| |type |String| 被触发的事件的类型| 当需要通过一个函数处理多个事件时,可以使用 type 属性 ``` var btn = document.getElementById('myBtn') var handler = function (event) { switch(event.type) { case 'click': alert('Clicked') break case 'mouseover': event.target.style.backgroundColor = 'red' break case 'mouseout': event.target.style.backgroundColor = '' break } } btn.onclick = handler btn.onmouseover = handler btn.onmouseout = handler ``` 要阻止特定事件的默认行为,可以使用 `preventDefault()`方法。例如,链接的默认行为就是在被单击时会导航到其 href 特性指定的 URL。如果你想阻止链接导航这一默认行为,那么通过链接的 onclick 事件处理程序可以取消它,如下面的例子所示 ``` var link = document.getElementById('myLink') link.onclick = function (event) { event.preventDefault() } ``` [target 与 currentTarget](https://www.cnblogs.com/yewenxiang/p/6171411.html) # 事件类型 常见的事件类型大致有以下几类: - UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发; - 焦点事件,当元素获得或失去焦点时触发; - 鼠标事件,当用户通过鼠标在页面上执行操作时触发; - 滚轮事件,当使用鼠标滚轮(或类似设备)时触发; - 文本事件,当在文档中输入文本时触发; - 键盘事件,当用户通过键盘在页面上执行操作时触发; - 变动(mutation)事件,当底层 DOM 结构发生变化时触发。 ## UI 事件 - load:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在`<img>`元素上面触发,或者当嵌入的内容加载完毕时在`<object>`元素上面触发。 - unload:当页面完全卸载后在 window 上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后在`<object>`元素上面触发。 - abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在`<object>`元素上面触发。 - error:当发生 JavaScript 错误时在 window 上面触发,当无法加载图像时在`<img>`元素上面触发,当无法加载嵌入内容时在`<object>`元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。 - select:当用户选择文本框(`<input>`或`<texterea>`)中的一或多个字符时触发。 - resize:当窗口或框架的大小变化时在 window 或框架上面触发。 - scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加载页面的滚动条。 ## 焦点事件 记住 blur 和 focus 即可 - blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它 - focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 ## 鼠标和滚轮事件 - click:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。 - dblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。 - mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。 - mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 - mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 - mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件 - mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。 - mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。不能通过键盘触发这个事件。 - mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件 ### 鼠标事件中的位置信息: 1.客户区坐标位置 clientX、clientY,表示事件发生时鼠标指针在视口中的水平位置和垂直位置,如图 ![](https://box.kancloud.cn/11ad405ba0c13ebbefd041513d71b17b_578x436.png) 2.页面坐标位置 pageX、pageY,这两个属性表示鼠标光标在页面中的位置,因此坐标是从页面本身而非视口的左边和顶边计算的,在页面没有滚动的情况下,pageX 和 pageY 的值与 clientX 和 clientY 的值相等 3.屏幕坐标位置 screenX、screenY,鼠标事件发生时,不仅会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。通过 screenX 和 screenY 属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息 ![](https://box.kancloud.cn/1b09f3fc9da8ee5a1856e26a3115319f_572x423.png) ### 修改键 虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键就是 Shift、Ctrl、Alt 和 Meta(在 Windows 键盘中是 Windows 键,在苹果机中 是 Cmd 键),它们经常被用来修改鼠标事件的行为。DOM 为此规定了 4 个属性,表示这些修改键的状态:`shiftKey`、`ctrlKey`、`altKey` 和 `metaKey`。这些属性中包含的都是布尔值,如果相应的键被按下了,则值为 true,否则值为 false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。 ### 相关元素 DOM 通过 event 对象的 `relatedTarget` 属性提供了相关元素的信息。这个属性只对于 mouseover 和 mouseout 事件才包含值;对于其他事件,这个属性的值是 null。 对 mouseover 事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对 mouseout 事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。 ### 鼠标滚轮事件 与 mousewheel 事件对应的 event 对象除包含鼠标事件的所有标准信息外, 还包含一个特殊的 `wheelDelta` 属性。当用户向前滚动鼠标滚轮时,wheelDelta 是 120 的倍数;当用 户向后滚动鼠标滚轮时,wheelDelta 是 120 的倍数。 ![](https://box.kancloud.cn/6a76318d8127a3de8babc283aecbba1b_303x241.png) 多数情况下,只要知道鼠标滚轮滚动的方向就够了,而这通过检测 wheelDelta 的正负号就可以确定 ### 触摸设备与鼠标事件 iOS 和 Android 设备的实现非常特别,因为这些设备没有鼠标。开发时,要记住以下几点。 - 不支持 dblclick 事件。双击浏览器窗口会放大画面,而且没有办法改变该行为。 - 轻击可单击元素会触发 mousemove 事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生 mousedown、mouseup 和 click 事件。轻击不可单击的元素不会触发任何事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了 onclick 事件处理程序的元素。 - mousemove 事件也会触发 mouseover 和 mouseout 事件。 - 两个手指放在屏幕上且页面随手指移动而滚动时会触发 mousewheel 和 scroll 事件。 ## 键盘与文本事件 有 3 个键盘事件,简述如下。 - keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。 - keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下 Esc 键也会触发这个事件。 - keyup:当用户释放键盘上的键时触发。 在用户按了一下键盘上的字符键时,首先会触发 keydown 事件,然后紧跟着是 keypress 事件,最后会触发 keyup 事件。其中,keydown 和 keypress 都是在文本框发生变化之前被触发的;而 keyup 事件则是在文本框已经发生变化之后被触发的。如果用户按下了一个字符键不放,就会重复触发 keydown 和 keypress 事件,直到用户松开该键为止。 **键码** 在发生 keydown 和 keyup 事件时,event 对象的 `keyCode` 属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode 属性的值与 ASCII 码中对应小写字母或数字的编码相同。因此,数字键 7 的 keyCode 值为 55,而字母 A 键的 keyCode 值为 65。 常用键及键码: | 键 | 键码 | 键 | 键码 | | --- | --- | --- | --- | | 退格 (Backspace) | 8 | 制表 (Tab) | 9 | | 回车 (Enter)| 13| 上档 (Shift)| 16 | |控制 (Ctrl) | 17| Alt | 18| | 暂停/中断 (Pause/Break)|19 |大写锁定 (Caps Lock) |20 | |数字小键盘1~9 |97~105 | 退出 (Esc)| 27| | 左箭头 (Left Arrow)|37 |上箭头 (Up Arrow) |38 | | 右箭头 (Right Arrow)| 39| 下箭头 (Down Arrow)| 40 | 也可以使用 `key` 和 `char` 属性来判断按键: key 属性是为了取代 keyCode 而新增的,它的值是一个字符串。在按下某个字符键时,key 的值就是相应的文本字符(如“k”或“M”);在按下非字符键时,key 的值是相应键的名(如“Shift” 或“Down”)。而 char 属性在按下字符键时的行为与 key 相同,但在按下非字符键时值为 null。 > IE9 支持 key 属性,但不支持 char 属性;这两个属性存在跨浏览器问题 # HTML5 事件 - DOMContentLoaded 事件:DOMContentLoaded 事件在形成完整的 DOM 树之后就会触发, 不理会图像、JavaScript 文件、CSS 文件或其他资源是否已经下载完毕。与 load 事件不同, DOMContentLoaded 支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。 - hashchange 事件:HTML5 新增了 hashchange 事件,以便在 URL 的参数列表(及 URL 中“#”号后面的所有字符串) 发生变化时通知开发人员。之所以新增这个事件,是因为在 Ajax 应用中,开发人员经常要利用 URL 参数列表来保存状态或导航信息。 必须要把 hashchange 事件处理程序添加给 window 对象,然后 URL 参数列表只要变化就会调用 它。此时的 event 对象应该额外包含两个属性:`oldURL` 和 `newURL`。这两个属性分别保存着参数列表变化前后的完整 URL。例如: ``` window.addEventListener('hashchange', function (event) { console.log(`old URL: ${event.oldURL} \n new URL: ${event.newURL}`) }) ``` # 触摸与手势事件 ## 触摸事件 - touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。 - touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用 preventDefault() 可以阻止滚动。 - touchend:当手指从屏幕上移开时触发。 除了常见的 DOM 属性外,触摸事件还包含下列三个用于跟踪触摸的属性。 - touches:表示当前跟踪的触摸操作的 Touch 对象的数组。 - targetTouchs:特定于事件目标的 Touch 对象的数组。 - changeTouches:表示自上次触摸以来发生了什么改变的 Touch 对象的数组。 ## 手势事件 当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项。有三个手势事件,分别介绍如下。 - gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。 - gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。 - gestureend:当任何一个手指从屏幕上面移开时触发 只有两个手指都触摸到事件的接收容器时才会触发这些事件。在一个元素上设置事件处理程序,意味着两个手指必须同时位于该元素的范围之内,才能触发手势事件(这个元素就是目标)。由于这些事件冒泡,所以将事件处理程序放在文档上也可以处理所有手势事件。此时,事件的目标就是两个手指都位于其范围内的那个元素。 触摸事件和手势事件之间存在某种关系。当一个手指放在屏幕上时,会触发 touchstart 事件。如果另一个手指又放在了屏幕上,则会先触发 gesturestart 事件,随后触发基于该手指的 touchstart 事件。如果一个或两个手指在屏幕上滑动,将会触发 gesturechange 事件。但只要有一个手指移开, 就会触发 gestureend 事件,紧接着又会触发基于该手指的 touchend 事件 # 事件委托 对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click 事件会一直冒泡到 document 层次。也就是说,我们可以为整个页面指定一个 onclick 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。以下面的 HTML 代码为例。 ```html <ul id="myLinks"> <li id="goSomewhere">Go somewhere</li> <li id="doSomething">Do something</li> <li id="sayHi">Say hi</li> </ul> ``` 按照传统的做法,需要像下面这样为它们添加 3 个事 件处理程序。 ```js var item1 = document.getElementById("goSomewhere") var item2 = document.getElementById("doSomething") var item3 = document.getElementById("sayHi") item1.addEventListener('click', function (event) { location.href = 'http://www.wrox.com' }) item2.addEventListener('click', function (event) { document.title = `I changed the documetns title` }) item3.addEventListener('click', function (event) { alert('hi') }) ``` 如果在一个复杂的 Web 应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序,如下面的例子所示。 ``` var list = document.getElementById('myLinks') list.addEventListener('click', function (event) { event = event ? event : window.event // 兼容 var target = event.target // 触发该事件的元素 switch(target.id) { case 'doSomething': document.title = `I changed the document's title` break case 'goSomewhere': location.href = 'http://www.wrox.com' break case 'sayHi': alert('Hi') break } }) ```