DOM事件
- 由此案例引入DOM事件
<div class="爷爷">
<div class="爸爸">
<div class="儿子"></div>
文字
</div>
</div>
- 给三个div分别添加事件监听fnYe/fnBa/fnEr
- 结论:点击文字,算点击儿子、爸爸、爷爷,都算
- 点击文字,最先调用哪一个函数? 都行
- IE5先调用儿子,网景(firefox)先调用爷爷
- 于是w3c发布标准
W3C标准(执行机制)
- 文档名为:DOM Level2 Events Specification
- 规定浏览器应该同时支持两种顺序
- 首先按由外向内顺序
- 然后按由内向外
- 有监听函数调用,并提供事件信息,没有就跳过
事件捕获、事件冒泡
- 从外到内找监听函数,叫事件捕获
- 从内向外找监听函数,叫事件冒泡 IE浏览器的
疑问:这样是否要执行两次?
不是,开发者要自己选择把函数放到捕获阶段还是冒泡阶段,取决于api传参
示意图(如下)
- 蓝色填充被点击
- 走两个过程,从window到属于捕获阶段,从到window属于冒泡阶段
addEventListener
- 事件绑定api
- IE5*:
baba.attachEvent('onclick',fn)冒泡 - 网景:
baba.addEventListener('click',fn)捕获 - W3C: `baba.addEventListener('click',fn,bool)
- IE5*:
- 如果bool不传参数或者为falsy值
- 让fn走冒泡,当发现监听函数,就调用fn
- 如果bool为true
- 让fn走捕获,当发现监听函数,调用fn
- 小结:W3C其实是默认支持冒泡的
- 注意:默认状态下两个路线是都会走的,顺序是固定的,只是开发者选择函数在哪条路执行,另一个路线空跑
- 事件捕获、冒泡形象展示:github.com/hxa0/DOM-ev…
示意图
小补充
target和currentTarget
- 区别:
- e.target是用户操作的元素、
- e.currrentTarget是监听的元素
- this是e.currentTarget
- 举例
- div>span{文字},用户点击文字
- e.target就是span
- e.currentTarget就是div
特殊情况
- 只有一个div被监听,不用考虑父子元素同时被监听时,fn分别在捕获和冒泡阶段监听事件,用户点击的元素就是开发者所监听的
举例
div.addEventListener('click',f1)div.addEventListener('click',f2,true)- 当不考虑父子元素,哪个在前,先执行哪个
取消冒泡
捕获不可取消,但是冒泡可以
- e.stopPrpagation()可中断冒泡,浏览器不在向上走
- 一般用于封装某些独立的组件
不可阻止默认动作
有些事件不能阻止默认动作
-
MDN搜索event,例如scrollevent,clickevent,英文文档有详细的介绍
-
例:搜索 scrollevent
阻止滚动
scroll事件不可阻止默认动作
- 阻止scroll默认动作不起作用,因为先有滚动才有滚动事件
- 要阻止滚动,可阻止wheel和touchstart的默认动作
- 注意要找准滚动条所在的元素
- 但是滚动条还能用,可用css将滚动条宽度设置为0
css设置也可以
- 使用overflow:hidden,可以直接取消滚动条
- 但此时JS依然可以修改scrollTop
代码实现
- 去掉滚动条
- 阻止滚轮事件
- 阻止触屏事件(手机端)
自定义事件
浏览器自带事件
- 一共100多种事件
- 列表:developer.mozilla.org/zh-CN/docs/…
案例
事件委托
场景一
- 要给100个按钮添加事件
- 方案:监听这100个按钮的父级元素,等冒泡的时候判断target是不是这100个按钮中的一个
代码
- div1为这100个按钮的父级元素,添加鼠标点击事件监听
- 声明变量t,t为用户操作的元素
- 如果t的标签名是button(转化成小写)
- 打印被点击的元素id
场景二
- 要监听目前不存在的元素的点击事件
- 方案:监听其父元素,等点击的时候判断是不是要监听的
代码
- div1里面不是立即出现button
- div1添加点击事件监听
- 声明变量t,t为操作的元素(点击的元素)
- 判断条件:如果操作的元素标签名字为button
- 打印button被点击,表明点击事件
封装事件委托
效果
- 写出一个函数on('click','#testDiv','li',fn)
- 当用户点击#tesDiv里的li元素时,调用fn函数
- 使用到事件委托
代码实现
- 定义on函数,接收参数:事件类型,元素,选择器(要匹配的元素),函数fn(调用的函数)
- 先判断,第二参数,如果不是元素,找这个元素
- 第二参数:元素,添加事件监听(机制参考前面内容)
- 判断如果被操作的元素匹配提供的选择器,那么就执行fn,并且把事件传参给fn
小补充
- 以上这个代码是有局限性的,仅适用于#div1>button(click)这种情况
场景变化
- 如果结构是#div!>button>span(click)
- 从下方代码的判断条件
if(t.matches(selector)),操作元素t是span标签,选择器是button标签,两个不匹配 - 选择器与实际操作的元素存在父子级关系的落差(不仅限于父子带,有可能相隔若干代)
方案
- 利用事件监听的最初逻辑,当点击某个元素,它的父级元素以及以上父级元素都算做被点击
- 递归判断target/target父元素/父元素的父元素/.....
代码
- 看被操作的元素是否是buttonbutton(line21)
- 如果不符合,就让这个元素等于其父元素(line26)
- 如果元素到了#div1这个级别,那么就要停止(不能超出div1这一级别),如果找不到(null),就停止了,不要找了(line 22-24)
- 最后判断如果找到的上级元素是匹配选择器的,调用fn,第一个参数传e,第二个参数传找到的元素(el),最开头传this(el) (line28)
- 以上代码可以整合进jQuery,调用:
$('#xxx').on('click''li',fn)(举例)
JS支持事件吗?
- DOM事件不属于JS的功能,属于浏览器的功能
- DOM事件与JS属于平行并列的功能
- JS只是调用了DOM提供的addEventListener功能而已
总结
DOM事件机制
- 事件捕获:当用户触发事件,浏览器会从window,由外向内(逐级从父代向子代),遍历到用户操作的元素,逐个触发处理事件的函数
- 事件冒泡:浏览器从用户操作的元素,由内向外(逐级从操作的元素向父代),遍历到window,逐个触发处理事件的函数
- 这个两个顺序是不会改变的,捕获过程不可中断,冒泡过程可以控制中断
- 遍历到的函数不是调用两次,开发者可以选择把函数放到任意一个阶段
事件委托机制
- 原理:在冒泡阶段,浏览器会从被操作元素逐级向上遍历,触发事件处理函数。
- 实现方法:当用户操作某个元素,不监听被操作元素本身,而是监听被操作元素的某个父级(或者以上)元素,触发事件处理函数。这样就能实现监听一个节点处理多个节点的效果
- 优点:节省内存,可以监听动态元素