事件目标
事件目标指产生事件的对象,比如 a 标签被点击那么 a 标签就是事件目标。元素是可以嵌套的,所以在进行一次点击行为时可能会触发多个事件目标。
事件处理程序
绑定事件处理程序
HTML 绑定
可以在 html 元素上设置事件处理程序,浏览器解析后会绑定到 DOM 属性中
<button onclick="alert(`houdunren.com`)">后盾人</button>
往往事件处理程序业务比较复杂,所以绑定方法或函数会很常见
绑定函数或方法时需要加上括号
<button onclick="show()">后盾人</button>
<script>
function show() {
alert('houdunren.com')
}
</script>
当然也可以使用方法做为事件处理程序
<input type="text" onkeyup="HD.show()" />
<script>
class HD {
static show() {
console.log('houdunren')
}
}
</script>
可以传递事件源对象与事件对象
<button onclick="show(this,'houdunren','hdcms','向军大叔',event)">后盾人</button>
<script>
function show(...args) {
console.log(args)
}
</script>
DOM 绑定
将事件处理程序作为事件目标对象或文档元素的一个属性,缺点就是后面注册的事件处理程序会把前面的事件给覆盖掉
也可以将事件处理程序绑定到 DOM 属性中
- 使用 setAttribute 方法设置事件处理程序无效
- 属性名区分大小写
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.onclick = function () {
this.style.color = 'red'
}
</script>
无法为事件类型绑定多个事件处理程序,下面绑定了多个事件处理程序,因为属性是相同的所以只有最后一个有效
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.onclick = function () {
this.style.color = 'red'
}
app.onclick = function () {
this.style.fontSize = '55px'
}
</script>
使用addEventListener注册
通过上面的说明我们知道使用 HTML 与 DOM 绑定事件都有缺陷,建议使用新的事件监听绑定方式 addEventListener 操作事件
使用 addEventListener 添加事件处理程序有以下几个特点
- transtionend / DOMContentLoaded 等事件类型只能使用 addEventListener 处理
- 同一事件类型设置多个事件处理程序,按设置的顺序先后执行
- 也可以对未来添加的元素绑定事件
| 方法 | 说明 |
|---|---|
| addEventListener | 添加事件处理程序 |
| removeEventListener | 移除事件处理程序 |
使用addEventListener方法,每个目标对象都有一个该方法,该方法可以实现为同一个目标同一个事件添加多个事件处理程序,该方法接受三个参数
-
- 第一个参数为事件类型
- 第二个参数为该事件的处理程序
- 第三个参数为该事件的配置选项,可以只是一个boolean值也可以是一个对象
📌 提示:可以使用removeEventListener方法来卸载事件处理程序,其处理程序是一样的
绑定多个事件
使用 addEventListener 来多个事件处理程序
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.addEventListener('click', function () {
this.style.color = 'red'
})
app.addEventListener('click', function () {
this.style.fontSize = '55px'
})
</script>
通过对象绑定
如果事件处理程序可以是对象,对象的 handleEvent 方法会做为事件处理程序执行。下面将元素的事件统一交由对象处理
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
class HD {
handleEvent(e) {
this[e.type](e)
}
click() {
console.log('单击事件')
}
mouseover() {
console.log('鼠标移动事件')
}
}
app.addEventListener('click', new HD())
app.addEventListener('mouseover', new HD())
</script>
事件选项
addEventListener 的第三个参数为定制的选项,可传递 object 或 boolean 类型
下面是传递对象时的说明
| 选项 | 可选参数 | |
|---|---|---|
| once | true/false | 只执行一次事件 |
| capture | true/false | 事件是在捕获/冒泡哪个阶段执行,true:捕获阶段 false:冒泡阶段 |
| passive | true/false | 声明事件里不会调用 preventDefault(),可以减少系统默认行为的等待。传入了 passive选项,该选项会导致preventDefault()无效 |
下面使用 once:true 来指定事件只执行一次
<button id="app">houdunren.com</button>
<script>
const app = document.querySelector('#app')
app.addEventListener(
'click',
function () {
alert('houdunren@向军大叔')
},
{ once: true }
)
</script>
设置 { capture: true } 或直接设置第三个参数为 true 用来在捕获阶段执行事件
addEventListener 的第三个参数传递 true/false 和设置 {capture:true/false}是一样
<div id="app" style="background-color: red">
<button id="bt">houdunren.com</button>
</div>
<script>
const app = document.querySelector('#app')
const bt = document.querySelector('#bt')
app.addEventListener(
'click',
function () {
alert('这是div事件 ')
},
{ capture: true }
)
bt.addEventListener(
'click',
function () {
alert('这是按钮事件 ')
},
{ capture: true }
)
</script>
设置 { capture: false } 或直接设置第三个参数为 false 用来在冒泡阶段执行事件
<div id="app" style="background-color: red">
<button id="bt">houdunren.com</button>
</div>
<script>
const app = document.querySelector('#app')
const bt = document.querySelector('#bt')
app.addEventListener(
'click',
function () {
alert('这是div事件 ')
},
{ capture: false }
)
bt.addEventListener(
'click',
function () {
alert('这是按钮事件 ')
},
{ capture: false }
)
</script>
移除事件处理程序
使用 removeEventListener 删除绑定的事件处理程序
<div id="app">houdunren.com</div>
<button id="hd">删除事件</button>
<script>
const app = document.querySelector('#app')
const hd = document.querySelector('#hd')
function show() {
console.log('APP我执行了')
}
app.addEventListener('click', show)
hd.addEventListener('click', function () {
app.removeEventListener('click', show)
})
</script>
调用事件处理程序
调用事件处理程序的上下文
在通过设置属性注册事件处理程序时,看起来就像为目标对象定义了一个新方法:
target.onclick = function() {/* 处理程序的代码 */};
因此,没有意外,这个事件处理程序将作为它所在对象的方法被调用。换句话说,在事件处理程序的函数体中,this关键字引用的是注册事件处理程序的对象。即便使用addEventListener()注册,处理程序在被调用时也会以目标作为其this值。 不过,这不适用于箭头函数形式的处理程序。箭头函数中this的值始终等于定义它的作用域的this值
调用事件处理程序的返回值
在现代JavaScript中,事件处理程序不应该返回值。在比较老的代码中,我们还可以看到返回值的事件处理程序,而且返回的值通常用于告诉浏览器不要执行与事件相关的默认动作。比如,如果一个表单Submit按钮的。onclick处理程序返回false,浏览器将不会提交表单(通常因为事件处理程序确定用户输入未能通过客户端验证)。
调用事件处理程序所传入的事件对象
执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对象。系统会自动做为参数传递给事件处理程序。
- 大部分浏览器将事件对象保存到 window.event 中
- 有些浏览器会将事件对象做为事件处理程序的参数传递
事件对象常用属性如下:
| 属性 | 说明 |
|---|---|
| type | 事件类型 |
| target | 事件目标对象,冒泡方式时父级对象可以通过该属性找到在哪个子元素上最终执行事件 |
| currentTarget | 当前执行事件的对象 |
| timeStamp | 事件发生时间 |
| x | 相对窗口的 X 坐标 |
| y | 相对窗口的 Y 坐标 |
| clientX | 相对窗口的 X 坐标 |
| clientY | 相对窗口的 Y 坐标 |
| screenX | 相对计算机屏幕的 X 坐标 |
| screenY | 相对计算机屏幕的 Y 坐标 |
| pageX | 相对于文档的 X 坐标 |
| pageY | 相对于文档的 Y 坐标 |
| offsetX | 相对于事件对象的 X 坐标 |
| offsetY | 相对于事件对象的 Y 坐标 |
| layerX | 相对于父级定位的 X 坐标 |
| layerY | 相对于父级定位的 Y 坐标 |
| path | 冒泡的路径 |
| altKey | 是否按了 alt 键 |
| shiftKey | 是否按了 shift 键 |
| metaKey | 是否按了媒体键 |
| window.pageXOffset | 文档参考窗口水平滚动的距离 |
| window.pageYOffset | 文档参考窗口垂直滚动的距离 |
事件传播
事件捕获
事件捕获是由外到内的。在事件捕获阶段,事件会从 DOM 树的最外层开始,依次经过目标节点的各个父节点,并触发父节点上的事件,直至到达事件的目标节点。如下图。
<script>
window.addEventListener("click",()=>{
console.log("Window");
},true);
document.addEventListener("click",()=>{
console.log("Document");
},true);
document.querySelector("#box2").addEventListener("click",()=>{
console.log("box2");
},true);
document.querySelector("#box1").addEventListener("click",()=>{
console.log("box1");
},true);
document.querySelector("a").addEventListener("click",()=>{
console.log("Click me");
},true);
</script>
事件冒泡
标签元素是嵌套的,在一个元素上触发的事件,同时也会向上执行父级元素的事件处理程序,一直到 HTML 标签元素。
- 大部分事件都会冒泡,但像 focus 事件则不会
- event.target 可以在事件链中最底层的定义事件的对象
- event.currentTarget == this 即当前执行事件的对象
上面我们说了事件捕获,事件冒泡和事件捕获是完全相反的。事件冒泡是由内到外的,它是从目标节点开始,沿父节点依次向上,并触发父节点上的事件,直至文档根节点,就像水底的气泡一样,会一直向上。如下图。
<script>
window.addEventListener("click",()=>{
console.log("Window");
},false);
document.addEventListener("click",()=>{
console.log("Document");
},false);
document.querySelector("#box2").addEventListener("click",()=>{
console.log("box2");
},false);
document.querySelector("#box1").addEventListener("click",()=>{
console.log("box1");
},false);
document.querySelector("a").addEventListener("click",()=>{
console.log("Click me");
},false);
</script>
事件取消
阻止默认行为
JS 中有些对象会设置默认事件处理程序,比如 A 链接在点击时会进行跳转。
一般默认处理程序会在用户定义的处理程序后执行,所以我们可以在我们定义的事件处理中取消默认事件处理程序的执行。
- 使用 onclick 绑定的事件处理程序,return false 可以阻止默认行为
- 推荐使用
event.preventDefault()阻止默认行为
下面阻止超链接的默认行为
<a href="https://www.houdunren.com">后盾人</a>
<script>
document.querySelector('a').addEventListener('click', () => {
event.preventDefault()
alert(event.target.innerText)
})
</script>
阻止事件传播
👋
注意:event.stopPropagation()方法不仅可以用于事件冒泡,还可用于事件捕获,下面只是用事件冒泡来展示event.stopPropagation()的使用
冒泡过程中的任何事件处理程序中,都可以执行 event.stopPropagation() 方法阻止继续进行冒泡传递
- event.stopPropagation() 用于阻止冒泡
- 如果同一类型事件绑定多个事件处理程序 event.stopPropagation() 只阻止当前的事件处理程序
- event.stopImmediatePropagation() 阻止事件冒泡并且阻止相同事件的其他事件处理程序被调用
下例中为 事件处理程序添加了阻止冒泡动作,将不会产生冒泡,也就不会执行父级中的事件处理程序了。
<script>
window.addEventListener("click",(e)=>{
console.log("Window");
e.stopPropagation();
},false);
document.addEventListener("click",(e)=>{
console.log("Document");
e.stopPropagation();
},false);
document.querySelector("#box2").addEventListener("click",(e)=>{
console.log("box2");
e.stopPropagation();
},false);
document.querySelector("#box1").addEventListener("click",(e)=>{
console.log("box1");
e.stopPropagation();
},false);
document.querySelector("a").addEventListener("click",(e)=>{
console.log("Click me");
e.stopPropagation();
},false);
</script>
以上代码如果将 event.stopPropagation() 替换为 event.stopImmediatePropagation() ,那么其他同类型的事件处理程序将不执行,同时阻止冒泡。
事件类型
比如点击事件还是键盘事件还是程序加载完的事件,不同事件有不类型,下面列举了一些常见的事件
窗口事件
| 事件名 | 说明 |
|---|---|
| window.onload | 文档解析及外部资源加载后 |
| DOMContentLoaded | 文档解析后执行,不需要等待图片/样式文件等外部资源加载,该事件只能通过 addEventListener 设置 |
| window.beforeunload | 文档刷新或关闭时 |
| window.unload | 文档卸载时 |
| scroll | 页面滚动时 |
鼠标事件
| 事件名 | 说明 |
|---|---|
| click | 鼠标单击事件,同时触发 mousedown/mouseup |
| dblclick | 鼠标双击事件 |
| contextmenu | 点击右键后显示的所在环境的菜单 |
| mousedown | 鼠标按下 |
| mouseup | 鼠标抬起时 |
| mousemove | 鼠标移动时 |
| mouseover | 鼠标移动时 |
| mouseout | 鼠标从元素上离开时 |
| mouseup | 鼠标抬起时 |
| mouseenter | 鼠标移入时触发,不产生冒泡行为 |
| mosueleave | 鼠标移出时触发,不产生冒泡行为 |
| oncopy | 复制内容时触发 |
| scroll | 元素滚动时,可以为元素设置 overflow:auto; 产生滚动条来测试 |
鼠标事件产生的事件对象包含相对应的属性
| 属性 | 说明 |
|---|---|
| which | 执行 mousedown/mouseup 时,显示所按的键 1 左键,2 中键,3 右键 |
| clientX | 相对窗口 X 坐标 |
| clientY | 相对窗口 Y 坐标 |
| pageX | 相对于文档的 X 坐标 |
| pageY | 相对于文档的 Y 坐标 |
| offsetX | 目标元素内部的 X 坐标 |
| offsetY | 目标元素内部的 Y 坐标 |
| altKey | 是否按了 alt 键 |
| ctrlKey | 是否按了 ctlr 键 |
| shiftKey | 是否按了 shift 键 |
| metaKey | 是否按了媒体键 |
| relatedTarget | mouseover 事件时从哪个元素来的,mouseout 事件时指要移动到的元素。当无来源(在自身上移动)或移动到窗口外时值为 null |
键盘事件
针对键盘输入操作的行为有多种事件类型
| 事件名 | 说明 |
|---|---|
| Keydown | 键盘按下时,一直按键不松开时 keydown 事件会重复触发 |
| keyup | 按键抬起时 |
键盘事件产生的事件对象包含相对应的属性
| 属性 | 说明 |
|---|---|
| keyCode | 返回键盘的 ASCII 字符数字 |
| code | 按键码,字符以 Key 开始,数字以 Digit 开始,特殊字符有专属名子。左右 ALT 键字符不同。不同布局的键盘值会不同 |
| key | 按键的字符含义表示,大小写不同。不能区分左右 ALT 等。不同语言操作系统下值会不同 |
| altKey | 是否按了 alt 键 |
| ctrlKey | 是否按了 ctlr 键 |
| shiftKey | 是否按了 shift 键 |
| metaKey | 是否按了媒体键 |
表单事件
下面是可以用在表单上的事件类型
| 事件类型 | 说明 |
|---|---|
| focus | 获取焦点事件 |
| blur | 失去焦点事件 |
| element.focus() | 让元素强制获取焦点 |
| element.blur() | 让元素失去焦点 |
| change | 文本框在内容发生改变并失去焦点时触发,select/checkbox/radio 选项改变时触发事件 |
| input | Input、textarea 或 select 元素的 value 被修改时,会触发 input 事件。而 change 是鼠标离开后或选择一个不同的 option 时触发。 |
| submit | 提交表单 |
派发自定义事件
如果一个JavaScript对象有addEventListener方法,那它就是一个“事件目标" 这意味着该对象也有一个dlspatchEvent()方法。可以通过CustonEvent构造函数创建自定义事件对象,然后再把它传给dispatchEvent。CustonEvent的第一个参数是一个字符串,表示事件类型,第二个参数是一个对象,用于指定事件对象的属性。可以将这个对象的detail属性设置为一个字符串、对象或其他值,表示事件的上下文。如果你想在一个文档元素上派发自己的事件,并希望它沿文档树向上冒泡,则要在第二个参数中添加bubbles: true。
<script>
document.addEventListener('cusEvent', () => {
console.log('自定义事件触发了')
})
document.dispatchEvent(
new CustomEvent('cusEvent', {
detail: 'context',
bubbles: true,
}),
)
</script>