一、Event对象
1、简介
事件event
对象是指在浏览器中触发事件时,浏览器会自动创建一个event
对象,其中存储了本次事件相关的信息,包括事件类型、事件目标、触发元素等等。浏览器创建完event
对象之后,会自动将该对象作为参数传递给绑定的事件处理函数,我们可以在事件处理函数中通过访问event
对象的属性和方法,来获取事件的相关信息,并做出后续的逻辑处理。
事件可以由用户操作触发,例如:鼠标事件、键盘事件等等;也可以通过 JS 脚本代码来触发,例如:通过element.click()
方法,触发对应元素的点击事件;还可以由API生成,例如:动画完成后触发对应事件、视频播放被暂停时触发对应事件;最后还可以通过自定义事件来进行触发。这四种方式都会创建并传递event
对象。
除了我们自己绑定的事件处理函数之外,浏览器会对某些元素的某些事件存在默认处理,例如: a 标签的click
点击事件,默认处理为跳转到链接的地址。当然我们也可以通过event
对象的preventDefault()
方法来阻止浏览器的默认处理。
我们还需要注意元素的嵌套关系,因为由于元素结构上的重叠,事件会随着嵌套关系依次传递,也就是事件冒泡和事件捕获。
2、Event对象示例
3、常用属性
① event.bubbles(只读)
该属性值为布尔值,表示当前事件是否会进行冒泡,true
表示会冒泡,false
表示不会冒泡。在前端事件大部分都是默认冒泡的,例如:click
、mousedown
等等;但也有一些特殊事件是默认不冒泡的,例如:focus
、blur
等等。
② event.cancelBubble
该属性值为布尔值,表示是否阻止当前事件的冒泡,默认为false
,不阻止冒泡;设置为true
则表示阻止当前事件冒泡。目前该属性已经完全被stopPropagetion()
方法所取代,虽然部分浏览器处于兼容性的考虑依旧支持该属性,但后续随时有可能停止支持,因此尽量不要使用该属性。
③ event.cancelable(只读)
该属性值为布尔值,表示当前事件的默认行为是否可以被取消,即是否能通过event.preventDefault()
来取消默认行为,true
表示可以取消,false
表示不可以取消。
如果是自定义事件,则在初始化事件时可以声明该事件是否可以被取消。
④ event.composed(只读)
该属性值为布尔值,曾用名:scoped
,表示当前事件是否可以打破屏障,从Shadow DOM
冒泡传递到普通DOM,如果值为true
表示当事件可以冒泡,即bubbles
属性为true
时,事件可以从Shadow DOM
冒泡传递到普通DOM,此时可以通过composedPath()
方法,来获取事件冒泡的路径;如果值为false
,则表示事件不会跨越Shadow DOM
与普通DOM的屏障进行冒泡。
关于Shadow DOM
的概念,在下面有进行讲解。
⑤ event.currentTarget(只读)
该属性值为一个DOM,表示当前事件所绑定的那个DOM元素。而且要注意的是,该属性值只能在事件处理函数的过程中event.currentTarget
直接调用使用,如果我们通过console.log(event)
输出事件对象,如同上面的示例,我们会发现该属性值的值为null
,无法访问到正确的值。我们在调试阶段,还可以通过在事件处理函数中设置debugger
暂停代码执行,从而在输出的event
对象上看到该属性正确的值。
⑥ event.target(只读)
该属性值为一个DOM,表示触发当前事件的那个DOM元素。通常情况下该属性的值与event.currentTarget
相同,但是如果当前事件是在冒泡或者捕获阶段被调用,则两者的值不同,target
的值为触发事件的DOM,currentTarget
的值为绑定事件的DOM。
我们还可以借助target
属性实现事件委托,又称事件代理,是指在要对一批子元素设置类似的事件处理器时,利用事件冒泡的机制,将事件处理器绑定到其公共父元素上,然后通过target
属性,来区分是由哪个子元素触发的事件,并进行相应的逻辑处理。这样即可以大大的减少事件处理器的数量,提高性能,又可以动态地添加或删除子元素而不需要添加或删除相应的事件处理器,减少代码复杂度。
target与currentTarget在冒泡阶段对比
案例代码:
<div id="divBox">
<div id="divClick">
这里触发点击事件
</div>
</div>
<script>
// 事件绑定在子元素上 此时 target =
document.querySelector('#divClick').addEventListener('click', function (e) {
console.log(e)
console.log(e.target)
console.log(e.currentTarget)
})
document.querySelector('#divBox').addEventListener('click', function (e) {
console.log(e)
console.log(e.target)
console.log(e.currentTarget)
})
</script>
执行结果:
⑦ event.defaultPrevented(只读)
该属性值为布尔值,表示当前事件是否调用了event.preventDefault()
方法,从而阻止了浏览器的默认行为,true
表示已经调用过,false
表示还未调用。
⑧ event.returnValue
属性值为布尔值,表示当前事件的默认行为是否执行,该属性可读可写,true
表示正常执行,false
表示阻止默认行为。该属性是由IE率先引入,最后被收入web规范,该属性相当于event.defaultPrevented
和event.preventDefault()
的结合。
案例代码:
<a href="https://www.baidu.com" id="a0">不阻止默认行为</a>
<a href="https://www.baidu.com" id="a1">event.returnValue阻止默认行为</a>
<a href="https://www.baidu.com" id="a2">event.preventDefault()阻止默认行为</a>
<script>
document.querySelector('#a0').addEventListener('click', function (e) {
// 不阻止默认行为
console.log('不阻止默认行为的e.returnValue---', e.returnValue)
// 打断点 暂停页面跳转 方便查看控制台输出
debugger
})
document.querySelector('#a1').addEventListener('click', function (e) {
// 阻止默认行为
e.returnValue = false
console.log('通过returnValue = false阻止默认行为的e.returnValue---', e.returnValue)
})
document.querySelector('#a2').addEventListener('click', function (e) {
// 阻止默认行为
e.preventDefault()
console.log('通过preventDefault()阻止默认行为的e.returnValue---', e.returnValue)
})
</script>
执行结果:
⑨ event.eventPhase(只读)
该属性值为整数数值,表示事件流的当前执行阶段,共分为四个阶段,每个阶段有不同的事件阶段常量,如下:
常量 | 值 | 描述 |
---|---|---|
Event.NONE | 0 | 这个阶段,没有事件正在被处理 |
Event.CAPTURING_PHASE | 1 | 这个阶段是指事件捕获的过程,事件正在被目标元素的祖先对象处理,是从最外层的祖先元素到目标元素的过程,从Window 、Document 、…、目标元素 的过程。 |
Event.AT_TARGET | 2 | 这个阶段是指到达目标元素的过程。如果 Event.bubbles 的值为 false ,即事件不会冒泡,则对事件对象的处理在这个阶段后就会结束。 |
Event.BUBBLING_PHASE | 3 | 这个阶段是指事件冒泡的过程,从从目标元素到最外层的祖先元素的过程。 |
事件流的执行过程:
当我们通过event.eventPhase
获取当前事件流的执行阶段时,会直接获取到当前事件阶段常量的值,我们可以直接根据值判断事件流的执行阶段,也可以通过对比event.eventPhase
与事件阶段常量是否强相等(===
),判断事件流的执行阶段。
案例代码:
<div id="div0">
<div id="div1">事件流演示</div>
</div>
<script>
// addEventListener设置第三个参数为true 表示在捕获阶段触发事件
document.querySelector('#div0').addEventListener('click', function (e) {
console.log('div0--', e)
// 输出事件阶段常量的值
console.log('捕获阶段的事件阶段常量---', e.eventPhase)
// 判断事件阶段是否为捕获阶段
console.log(e.eventPhase === Event.CAPTURING_PHASE)
}, true)
// addEventListener设置第三个参数为false或者不设置 表示在冒泡阶段触发事件
document.querySelector('#div0').addEventListener('click', function (e) {
console.log('div0--', e)
// 输出事件阶段常量的值
console.log('冒泡阶段的事件阶段常量---', e.eventPhase)
// 判断事件阶段是否为冒泡阶段
console.log(e.eventPhase === Event.BUBBLING_PHASE)
})
// 目标元素
document.querySelector('#div1').addEventListener('click', function (e) {
console.log('div1--', e)
// 输出事件阶段常量的值
console.log('到达目标元素的事件阶段常量---', e.eventPhase)
// 判断事件阶段是否为到达目标元素阶段
console.log(e.eventPhase === Event.AT_TARGET)
})
</script>
执行结果:
⑩ event.timeStamp(只读)
该属性值为一个数值,表示从文档加载完成,到当前事件被触发之间的的毫秒数,Chrome、Safari和Edge返回的是带有小数的毫秒数,FireFox返回的是不带小数的毫秒数。
因为各大浏览器之间的返回值并不统一,所以不太建议使用该属性。
案例代码:
<div style="width: 200px;height: 30px;" id="div2">点击获取事件发生时的毫秒数</div>
<script>
// 获取事件发生时的毫秒数
document.querySelector('#div2').addEventListener('click', function (e) {
console.log('事件发生时的毫秒数---', e.timeStamp)
})
</script>
执行结果:
⑪ event.type(只读)
该属性值是一个字符串,表示当前事件的类型,不区分大小写。
案例代码:
<div style="width: 200px;height: 30px;" id="div3">
点击获取事件的type属性
</div>
<script>
// 获取事件的type属性
document.querySelector('#div3').addEventListener('click', function (e) {
console.log('事件的type属性---', e.type)
})
</script>
执行结果:
⑫ event.isTrusted(只读)
该属性值是一个布尔值,表示当前事件是由用户行为触发,还是通过JS脚本触发,true
表示用户行为触发,false
表示脚本触发。
案例代码:
<div style="width: 200px;height: 30px;" id="div4">点击获取事件的isTrusted属性</div>
<div id="div5"></div>
<script>
// 获取用户手动触发时事件的isTrusted属性
document.querySelector('#div4').addEventListener('click', function (e) {
console.log('用户手动触发时事件的isTrusted属性---', e.isTrusted)
// 设置定时器模拟脚本触发事件
setTimeout(() => {
document.querySelector('#div5').click();
}, 1000)
})
// 获取脚本触发时事件的isTrusted属性
document.querySelector('#div5').addEventListener('click', function (e) {
console.log('脚本触发时事件的isTrusted属性---', e.isTrusted)
})
</script>
执行结果:
4、常用方法
① event.composedPath()
该方法用来获取当前事件的事件传播路径,从触发元素到最外层Window
,而且阻止冒泡的方法不会影响到事件的传播路径。如果Shadow DOM
根节点触发事件,并且ShadowRoot.mode
是关闭的,则获取的路径中将不包括Shadow DOM
节点。
案例代码:
<div id="div0">
<div id="div0Son">
验证target和currentTarget
</div>
</div>
<script>
// 获取事件对象的事件路径
document.querySelector('#div0Son').addEventListener('click', function (e) {
// 阻止事件冒泡 但并不会影响事件的传播路径
e.stopPropagation()
// 通过e.composedPath()获取事件对象的事件路径
console.log(e.composedPath())
})
</script>
执行结果:
② event.preventDefault()
该方法用来取消当前事件的默认行为,当然如果当前事件存在冒泡行为,即bubbles
属性为true
,那么事件的冒泡行为还是会继续向上传播,不会被影响。
案例代码:
<div id="div1">
<input type="checkbox" id="checkbox">
</div>
<script>
// 阻止多选按钮的默认行为
document.querySelector('#checkbox').addEventListener('click', function (e) {
e.preventDefault()
})
// 验证阻止默认行为后是否会继续冒泡
document.querySelector('#div1').addEventListener('click', function (e) {
console.log('阻止默认行为后会继续冒泡')
})
</script>
执行结果:
③ event.stopPropagation()
该方法用来阻止当前事件在捕获阶段和冒泡阶段中的传播,如果点击了子元素,但是在子元素中阻止了事件的冒泡,那么父元素对应的事件不会被触发;如果点击了子元素,但是在父元素中阻止了事件的捕获传播,那么子元素对应的事件将不会被触发。
案例代码:
<div id="div2">
<div id="div2Son">
验证stopPropagation()方法阻止事件冒泡传播
</div>
</div>
<div id="div3">
<div id="div3Son">
验证stopPropagation()方法阻止事件捕获传播
</div>
</div>
<script>
// 验证stopPropagation()方法阻止事件冒泡
document.querySelector('#div2Son').addEventListener('click', function (e) {
console.log('子元素阻止事件冒泡');
e.stopPropagation()
})
// 验证stopPropagation()方法阻止事件冒泡后父元素是否接收到冒泡的事件
document.querySelector('#div2').addEventListener('click', function (e) {
console.log('父元素是否接收到冒泡的事件')
})
// 验证stopPropagation()方法阻止事件捕获传播
document.querySelector('#div3').addEventListener('click', function (e) {
console.log('父元素阻止事件捕获传播');
e.stopPropagation()
}, true)
// 验证stopPropagation()方法阻止事件捕获后子元素是否接收到捕获的事件
document.querySelector('#div3Son').addEventListener('click', function (e) {
console.log('子元素是否接收到捕获传播的事件')
}, true)
</script>
执行结果:
④ event.stoplmmediatePropagation()
该方法用来阻止当前事件的其他事监听器被触发,如果我们通过addEventListener()
方法给同一个事件,增加多个事件监听器,那当该事件被触发时,多个事件监听器会按照添加的顺序依次执行。但如果我们在其中某个事件监听器绑定的函数中,执行event.stoplmmediatePropagation()
方法,那么位于该事件监听器之后的其他监听器将不会被触发。
案例代码:
<div id="div4">
验证stopImmediatePropagation()法
</div>
<script>
// 验证stopImmediatePropagation()方法
document.querySelector('#div4').addEventListener('click', function (e) {
console.log('第一个事件监听器');
})
document.querySelector('#div4').addEventListener('click', function (e) {
console.log('第二个事件监听器');
// 阻止后续事件监听器的执行
e.stopImmediatePropagation()
})
document.querySelector('#div4').addEventListener('click', function (e) {
console.log('第三个事件监听器');
})
</script>
执行结果:
5、Shadow DOM
Shadow DOM 是Web components 的核心内容,用于构建独立的web组件,可以将一个DOM树和相应的CSS样式封装隔离起来,与页面的常规DOM相互隔离,不会出现影响和冲突。然后可以将封装的Shadow DOM附加到常规的DOM节点上。操作Shadow DOM 的方式与常规DOM并无太大区别,都可以给DOM添加子节点、设置属性,以及为节点添加自己的样式。唯一有所不同的是:Shadow DOM内部的元素除了设置 :focus-within
之外,设置其他任何属性都不会影响到常规 DOM。
Shadow DOM 有以下四个主要概念:
- Shadow host:一个常规的DOM节点,Shadow DOM将会附加在该节点上。
- Shadow tree:一个DOM树,即Shadow DOM 内部的DOM结构。
- Shadow boundary:Shadow DOM与常规DOM的分界线,是 Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
- Shadow root :Shadow tree所代表的DOM树的根节点。
Shadow DOM主要概念图解:
虽然Shadow DOM看起来比较陌生,但这并不是一个新特性,浏览器很早之前便在使用该特性,例如:<video>
标签,虽然我们在DOM中只能看到一个单独的标签,但其实在该标签的Shadow DOM中,包含了进度条、播放按钮、倍速按钮等DOM结构。 Firefox(从版本 63 开始)、Chrome、Opera 、 Safari、Edge 都默认支持 Shadow DOM。
二、自定义事件
1、简介
在日常开发中我们通常使用的都是浏览器提前定义好的事件,例如:点击事件click
、失去焦点事件blur
等等。当这些基础事件不能满足我们的业务需求时,我们也可以通过创建自定义Event事件对象来自定义相关事件。
目前实现自定义事件的方式有两种:Event()
构造函数和CustomEvent()
构造函数。
触发自定义事件的方式为:element.dispatchEvent(event)
。
2、Event()
构造函数Event(typeArg[,eventInit])
用来创建一个新的event
事件对象,该构造函数拥有两个参数:第一个参数typeArg
为必填参数,参数值为DOMString
类型,表示所创建事件的名称;第二个参数eventInit
为可选参数,参数值为EventInit
类型的对象数据,拥有三条可选属性:① bubbles
:属性值为Boolean
类型,默认值为false
,表示创建的事件是否能进行冒泡。② cancelable
:属性值为Boolean
类型,默认值为false
,表示创建的事件的行为是否可以被preventDefault()
取消。③ composed
: 属性值为为Boolean
类型,默认值为false
,表示创建的事件是否可以穿透Shadow DOM
与常规DOM的屏障,进行冒泡。
案例代码:
<div class="div1">
<div class="div1Son">
验证自定义事件
</div>
</div>
<script>
// 1、创建一个支持冒泡,支持取消,支持穿透 的look事件
let myEvent = new Event('look', { "bubbles": true, "cancelable": true, "composed": true });
// 2、监听自定义事件
document.querySelector('.div1Son').addEventListener('look', function (e) {
console.log('监听到自定义事件----', e.type)
})
// 3、利用点击事件 去触发自定义事件
document.querySelector('.div1Son').addEventListener('click', function (e) {
// 触发自定义事件
document.querySelector('.div1Son').dispatchEvent(myEvent)
})
// 4、监听父元素是否收到冒泡的自定义事件
document.querySelector('.div1').addEventListener('look', function (e) {
console.log('父元素是否收到冒泡的自定义事件----', e.type)
})
</script>
执行结果:
3、CustomEvent()
构造函数CustomEvent(typeArg[,customEventInit])
用来创建一个新的CustomEvent
事件对象,该构造函数拥有两个参数:第一个参数typeArg
为必填参数,参数值为DOMString
类型,表示所创建事件的名称;第二个可选参数customEventInit
,参数值为CustomEventInit
类型的对象数据,拥有四条可选属性:① bubbles
:属性值为Boolean
类型,默认值为false
,表示创建的事件是否能进行冒泡。② cancelable
:属性值为Boolean
类型,默认值为false
,表示创建的事件的行为是否可以被preventDefault()
取消。③ composed
: 属性值为为Boolean
类型,默认值为false
,表示创建的事件是否可以穿透Shadow DOM
与常规DOM的屏障,进行冒泡。④ detail
:属性值为任意类型的数据,默认值为null
,通常用来表示与当前事件相关的信息。
Event
接口是 CustomEvent
接口的父级接口,因此 CustomEvent
继承了 Event
的所有属性和方法,并且在 Event
的基础上添加了一个 detail
属性,用于在事件处理程序中传递自定义数据。除此之外,CustomEvent
还允许我们在事件对象上添加自定义属性和方法,以便在事件处理程序中使用。这使得我们可以创建更灵活、更具体的事件类型,并在事件处理程序中访问和操作事件对象。
总的来说:CustomEvent
是自定义的事件类型,可操作性强,可以更好地满足开发人员的特定需求,而 Event
则更适合表示标准的、通用的事件类型,可操作性弱。
案例代码:
<div class="div2">
<div class="div2Son">
验证CustomEvent()构造函数创建自定义事件
</div>
</div>
<script>
// 利用CustomEvent()构造函数创建自定义事件
// 1、创建一个支持冒泡,支持取消,支持穿透,具有自定义属性 的look事件
let myEvent2 = new CustomEvent('look', {
"bubbles": true, "cancelable": true, "composed": false, detail: {
info: '这是CustomEvent的detail属性', // 自定义属性
getTime: function () { // 自定义方法
return new Date()
}
}
});
// 2、监听自定义事件
document.querySelector('.div2Son').addEventListener('look', function (e) {
console.log('监听到自定义事件----', e)
console.log('调用自定义事件上的自定义方法----', e.detail.getTime())
})
// 3、利用点击事件 去触发自定义事件
document.querySelector('.div2Son').addEventListener('click', function (e) {
// 触发自定义事件
document.querySelector('.div2Son').dispatchEvent(myEvent2)
})
// 4、监听父元素是否收到冒泡的自定义事件
document.querySelector('.div2').addEventListener('look', function (e) {
console.log('父元素是否收到冒泡的自定义事件----', e.type)
})
</script>
执行结果:
三、相关资料
事件参考
Event
CustomEvent
shadow DOM
:focus-within