什么是JS事件
在<<JS高级程序设计>>中,是这么定义事件的: 事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。以现在的眼光来看,其实不仅仅是在浏览器窗口了,JS在小程序、PWA等环境下,也在发挥其特定的作用。
事件处理程序
鼠标点击(click)、双击(dblclick),页面加载完成(load)都是事件。我们可以通过以下几种形式来为事件指定处理程序
- HTML事件处理程序
<input type="button" value="click me" onclick="alert(event.type)">
- DOM0级事件处理程序
let btn = document.querySelector('#btn') btn.onclick = function(){}
- DOM2级事件处理程序
也是我们最常是用的方式。通过addEventListener()和removeEventListener()来添加和移除。addEventListener接受三个参数:事件名称,回调函数和一个布尔值(true:捕获,默认false冒泡)。在IE浏览器上,则是使用attachEvent()和detachEvent()来添加和移除事件,另外在IE8极其更早版本,只支持事件冒泡。我们可以通过封装一个通用事件函数,做浏览器判断处理。let EventUtil = { addHandle(element,type,handle){ if(element.addEventListener){ element.addEventLister(type,handle,false)//现在基本上都是使用冒泡了 }else if(element.attachEvent){ element.attachEvent(`on${type}`,handle) }else{ element[`on${type}`] = handle } }, removeHandle(){ if(element.addEventListener){ element.removeEventListener(type,handle,false) }else if(element.attachEvent){ element.detachEvent(`on${type}`,handle) }else{ element[`on${type}`] = null } } }
事件流
我们设定一个大的DIV1,里面放DIV2,再在DIV2里面放入DIV3。在设计JS事件机制的时候,这个时候点击DIV3,有两种思路:
- 一种顺序是1=>2=>3, 先在1上触发,再依次往下,
- 另外一种思路是3=>2=>1,在最里面的先触发,依次往上。
前者就叫事件的捕获(event capturing),后者叫事件的冒泡(event bubbling)。
我们在每一个div上加上捕获,和冒泡的事件监听
document.querySelector('#box1').addEventListener('click', function() {
console.log('box1 捕获')
}, true)
document.querySelector('#box1').addEventListener('click', function() {
console.log('box1 冒泡')
}, false)
document.querySelector('#box2').addEventListener('click', function() {
console.log('box2 捕获')
}, true)
document.querySelector('#box2').addEventListener('click', function() {
console.log('box2 冒泡')
}, false)
document.querySelector('#box3').addEventListener('click', function() {
console.log('box3 捕获')
}, true)
document.querySelector('#box3').addEventListener('click', function() {
console.log('box3 冒泡')
}, false)
// 点击BOX3 最终执行结果是
box1 捕获 => box2 捕获 => box3 捕获 => box3 冒泡 => box2 冒泡 => box1 冒泡
在浏览器中,事件流分为三个阶段 捕获=>处于目标阶段=>冒泡。值得注意的是,处于目标阶段,HTML事件处理程序会先于捕获事件执行。
<div id="box3" onclick="console.log('box3 html event')"></div>
这个事件会在 box3捕获前执行
事件委托
如果事件处理程序过多,我们就要用到事件委托的机制了。事件委托主要是利用了事件冒泡的机制,通过在父元素上添加事件来处理相关逻辑。比如下面的列表,如果我们在每个li上添加事件,点击获取其id,如果li特别多,会导致内存占用高,引起性能问题,事件委托则不会。
<ul id="list">
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
</ul>
document.querySelector('#list').addEventListener('click',function(e){
console.log('e===>',e.target.id)
})
事件对象
属性/方法 | 类型 | 说明 |
---|---|---|
bubbles | Boolean | 事件是否冒泡 |
cancelable | Boolean | 是否可以取消事件的默认行为 |
currentTarget | Element | 事件处理程序当前正在处理的元素 |
defaultPrevented | Boolean | 为true表示调用了preventDefault() |
detail | Integer | 与事件相关的细节信息 |
eventPhase | Integer | 事件阶段1:捕获,2:处于目标,3:冒泡 |
preventDefault() | Function | 取消事件默认行为,需要cancel |
stopImmediatePropagation() | Function | 取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用 |
stopPropagation() | Function | 取消事件的进一步捕获或冒泡 |
target | Element | 事件目标 |
trusted | Boolean | 为true代表浏览器生成,为false表示由开发人员通过js |
type | String | 触发事件的类型 |
view | AbstractView | 与事件关联的抽象视图,等同于发生时间的window对象 |
在前面例子中,我们用到了e.target.id来获取li的id,这个参数e实际上是事件对象event,其包含了有关于这个时间的信息,比如类型,元素等。
这些属性中,要区分清楚地是currentTarget与target可能相同,也可能不同。当点击的事件正好是在事件目标上触发的时候,这两个值是相同的,否则不同。