1.什么是事件 ?
事件是浏览器赋予元素的默认行为,也可以理解为事件是天生具备的,不论我们是否为其绑定方法,当某些行为触发的时候,相关的事件都会被触发执行!
参考都有哪些事件 developer.mozilla.org/zh-CN/docs/…
-
鼠标事件
- click 点击事件(PC:频繁点击N次,触发N次点击事件) 单击事件(移动端:300ms内没有发生第二次点击操作,算作单击事件行为,所以click在移动端会有300ms延迟)
- dblclick 双击事件
- contextmenu 鼠标右键点击触发
-
键盘事件
- keydown keypress keyup
-
手指事件 (移动端)
- touchstart
- touchmove
- touchend
- touchcancel
- gestureStart(多个手指)
-
表单事件
-
视图事件
- resize
- scroll
-
资源事件
- load 加载成功 window.onload img.onload
- error 加载失败
- beforeunload 资源卸载之前 window.beforeunload 页面关闭之前触发
-
...
2.什么是事件绑定?
给元素默认的事件行为绑定方法,这样可以在行为触发的时候,执行这个方法。
-
DOM0级事件绑定
docoment.body.onclick = function(){} // 绑定 docoment.body.onclick = null // 删除原理:每一个DOM元素对象的私有属性上都有很多类似于 onxxx 的私有属性,我们给这些代表事件的私有属性赋值,就是DOM0事件绑定
- 如果没有对应事件的私有属性值,例如DOMContentLoaded, 则无法基于这种方法实现事件绑定
- 只能给当前元素的某个事件行为绑定一个方法,绑定多个方法,最后一个操作会覆盖以往的
- 好处是执行效率快,而且开发者使用起来方便。
DOM0级事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的。(参考下面的事件传播机制)
-
DOM2级事件绑定
document.body.addEventListener('click',fn1,[冒泡/捕获]==[false/true]) //(最后一个参数默认是false,控制方法是在冒泡阶段触发执行,如果设置为true可以控制在捕获阶段触发执行) document.body.removeEventListener('click',fn1,[冒泡/捕获]) //删除绑定的方法,后面参数和绑定的时候一样才可以原理:每一个DOM元素都会基于原型链的查找机制,找到EventTarget.prototype的addEventListener和removeEventListener等方法,基于这些方法实现事件的绑定和移除,DOM2级事件绑定采用事件池机制。放到EventQueue 事件队列中。有元素,事件类型,方法,阶段(捕获,冒泡)属性,所以多个方法可以依次注入到事件池里面。通过addEventListener注入,removeEventListener移除。是一种发布订阅模式。
- DOM2级事件绑定,绑定的方法一般不是匿名函数。匿名函数的话,删除不能删。
- 凡是浏览器提供的事件行为,都可以基于这种模式完成事件的绑定和移除。(DOM0中的window.onDOMContentLoaded是不行的,因为没有这个私有属性)但是我们可以 window.addEventListener('DOMContentLoaded',func) 这样是可以的
- 可以给当前元素的某个事件类型绑定多个 不同 的方法(进入到事件池)。这样事件触发,会从事件池中依次(按照绑定顺序)取出方法执行。
DOM2级事件绑定可以控制绑定的方法在捕获阶段触发(虽然没有实际意义)
3.事件对象
存储当前事件操作及触发的相关信息的(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)给当前元素的某个事件行为绑定方法,当事件行为触发,不仅会把绑定的方法执行,而且还会给方法默认传递一个实参,而这个实参就是我们的事件对象
-
鼠标事件对象 MouseEvent
- clientX/clientY 鼠标触发点距离当前窗口的X/Y轴坐标
- pageX/pageY 鼠标触发点距离Body的
- type 事件类型
- path 传播路径
- target/srcElement 获取当前的事件源(当前操作的元素)
- ev.preventDefault()/ ev.returnValue = false 阻止默认行为 这种两种表示一个意思的 都是为了适配兼容的写法
- ev.stopPropagation()/ ev.cancelBubble = true 阻止冒泡的
-
键盘事件对象 KeyboardEvent ------------------------例如keydown
- which/keyCode 获取按键的键盘码(window键盘)
- 方向键 左37 上38 右39 下40
- Space 32
- BackSpace 8
- Del 46
- Enter 13
- altkey 是否按了alt键
- ctrlKey
- shiftKey
- key/code 存储按键名字,但是这个可能有点不准
- 同上的阻止默认行为和冒泡传播的都有
- which/keyCode 获取按键的键盘码(window键盘)
-
手指事件对象 TouchEvent (移动端)
-
changedTouches/targetTouches/touches 记录手指信息的,平时常用changedTouches 手指按下,移动,离开屏幕 都存储了对应的信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕中的信息。=> 获取的结果都是一个TouchList集合,记录每一根手指的信息。例如 ev.changedTouches[0] 里面有 clientY pageY 等信息。
-
事件对象 Event
-
4.阻止默认行为
浏览器会赋予元素很多的默认行为操作
- 鼠标右键菜单
- 点击a标签跳转页面
- 部分浏览器会记录输入记录,下次再输入的时候 有模糊匹配
- 键盘按下会输入内容
- ...
我们可以基于ev.preventDefault() 来禁用这些默认行为
A标签的默认行为: href="javascript:;" 阻止锚点定位。
5.事件的传播机制
// 如下HTML
<div class="box">
<div class="outer">
<div class="inner">
</div>
</div>
</div>
// 层级关系为
window->
document->
html->
body->
box->
outer->
inner-> (点击)
-
阶段1:捕获阶段->从外往里找 从window 一直找到inner。
目的是为冒泡阶段的传播提供路径。ev.path就是存放捕获阶段的收集的传播路径
-
阶段2:目标阶段->触发事件源的相关事件行为
把当前事件源的相关事件行为触发
-
阶段3:冒泡阶段-> 由内向外
按照捕获阶段收集的传播路径,不仅仅当前事件源的相关事件行为被触发,而且从内到外,其祖先所有元素的相关事件行为也都会被触发(如果做了事件绑定,绑定的方法也会执行)
如上:inner的click行为触发,outer也会触发 一层层 box body 一直到最上面,事件都会被触发,如果绑定了方法,都会执行。
DOM0级事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的。
DOM2级事件绑定可以控制绑定的方法在捕获阶段触发(虽然没有实际意义) ;addEventListener第三个参数为true的时候 是在捕获阶段触发。
应用:
-
1.阻止冒泡
ev.stopPropagation?ev.stopPropagation():ev.cancelBubble=true ev.cancelBubble=true //兼容IE6~8的写法。 -
2.事件委托/事件代理
利用事件的冒泡传播机制(核心和前提),我们可以把一个容器A中,所有后代元素的某个事件行为触发操作,委托给当前容器A的某个事件行为。因为点击事件行为,存在冒泡传播机制,所以不论点击inner outer 还是box 最后都会传播到body上。触发body的事件行为,把为其绑定的方法执行。
优点:
-
性能高 60%左右
-
可以操作动态绑定的元素
-
某些需求必须基于它完成
// 基于委托判断 点击的是inner还是某个盒子 然后执行不同的代码 document.body.onclick= function(ev){ let target = ev.target let targetClass = target.className if(targetClass === 'inner'){ console.log('inner') return } if(targetClass === 'outer'){ console.log('outer') return } if(targetClass === 'box'){ console.log('box') return } }
-
6.mouseover 和 mouseenter区别
- mouseover 和 mouseout 存在事件的冒泡传播机制 会触发多次
- mouseenter 和 mouseleave 默认阻止了事件的冒泡传播机制 这个是我们需要的。不会触发多次
项目中,如果我们做 鼠标划入划出操作 有子元素的话 一般用mouseenter