今天我学习了DOM事件和事件委托,下面是我的笔记。
DOM事件
事件捕获
定义:从外向内找监听函数
事件冒泡
定义:从内向外找监听函数
addEventListener//事件绑定API
W3C:baba.addEventListener('click',fn,bool)
- 如果bool不传或者值为falsy→fn走冒泡(当浏览器在冒泡阶段发现baba有监听函数,就会调用fn,并提供事件信息)
- 如果bool为true→fn走捕获(当浏览器在不活阶段发现baba有fn监听函数,就会调用fn,并提供事件信息)
- 所以你可以选择改变bool的值来决定让fn先走冒泡还是先走捕获
那么如果一个DOM元素中,既有冒泡,又有捕获,会先执行冒泡还是捕获呢?
W3C规定,发生在w3c事件模型中的事件,先进入捕获阶段,达到目标元素(DOM元素绑定的事件被触发时,此时该元素为目标元素)之后,再进入冒泡阶段(所有事件的顺序是:其他元素捕获阶段事件→本元素代码顺序事件→其他元素冒泡阶段事件),这个可以这样理解:
one.addEventListener('click',function(){
console.log('one');
},true);//捕获
two.addEventListener('click',function(){
console.log('two');
},false);//冒泡
three.addEventListener('click',function(){
console.log('three');
},true);//捕获
four.addEventListener('click',function(){
console.log('four');
},false);//冒泡
此时当我们点击four元素,four为目标元素,one作为根元素祖先(无论目标元素是捕获还是冒泡,在W3C下都是先从根元素执行捕获到目标元素,再从目标元素向上执行。),因此从one元素开始执行,
one为捕获事件,执行;two为冒泡事件,忽略;three为冒泡事件,执行;four为冒泡事件,忽略;
再从目标元素向上执行,则为four→two,执行结果为"one"、"three"、"four"、"two"
当我们点击three元素,同理three元素变成了目标元素,one依然作为根元素,此时的执行结果为"one"、"three"、"two"
target和currentTarget区别
- e.target是用户点击
- e.currentTarget是开发者监听
- 监听代码中不建议使用this 当有如下代码:
<div>
<span>
{文字}
</span>
</div>
当用户点击文字时,e.target就是span, e.currentTarget就是div
- 但是当只有一个div被监听(不考虑父子同时被监听),fn分别在捕获阶段和冒泡阶段监听click事件,此时用户点击的就是开发者监听的,即谁先监听谁先执行。
取消冒泡(捕获不可以取消,但是冒泡可以)
- e.stopPropagation( ) 可以中断冒泡,一般用于封装某些独立的组件
不可取消冒泡(有些特殊事件不可取消冒泡)
- scroll event不可取消冒泡
- 但是我们可以通过阻止wheel和touchsheet的默认动作来取消滚动,滚动条还能用的话可以通过css让滚动条的width:0
事件委托
定义:委托一个元素帮我监听我本该监听的事件
监听祖先
- 如果我们要给如下3个按钮添加点击事件:
<div id="div1">
<button>one</button>
<button>two</button>
<button>three</button>
</div>
我们可以监听这三个按钮的祖先:
div1.addEventListener('click', (e)=>{
const t = e.target
if(t.tagName.toLowerCase() === "button"){
console.log('clicked')
console.log('button内容是'+t.textContent)
}
})
此时当我们点击one这个按钮的时候,输出的结果是
clicked
button内容是one
这样我们就通过监听祖先元素,给这三个按钮都加上了按钮点击事件
- 如果我们要监听一个目前不存在元素的点击事件,同样是上面三个按钮,但是目前他们并不存在,他们将在一秒钟之后出现:
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'one'
div1.appendChild(button)
},1000)
那么我们怎么用事件委托监听呢,同样的先监听祖先元素
div1.addEventListener('click',()=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('button clicked')
}
})
此时我们点击one,结果是
button clicked
我们发现此时div1是动态判断的,不管之前有什么元素,我们只看点击这一刻的标签名是不是button
- 综上我们发现事件委托的优点有:
- 省监听内存
- 可以监听动态元素
封装事件委托
接如上代码我们继续写到
on('click','#div1','button',()=>{
console.log('button clicked')
})
我们要如何实现只用on事件就可以做到在div1上面做事件委托,来看button有没有被点击呢?
function on(eventType, element, selector, fn){ //接收四个参数
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(e)=>{
const t = e.target
if(t.matches(selector)){ //matches判断一个元素是否匹配一个选择器
fn(e)
}
})
}
此时我们点击one,结果依然是
button clicked
但是注意:
- 如果当前元素不匹配button,我们就要看父元素是否匹配,只要祖先元素中的某一项是button,就代表我此时点击的是button,代码如下
function(eventType, element, selector, fn) {
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) { //当元素到达目标元素div1时
el = null
break
}
el = el.parentNode //不匹配button时,看父元素是否匹配
}
el && fn.call(el, e, el)
})
return element
}
这种办法叫做递归判断
那么JS支持事件吗?
不支持。以上所写均为DOM事件,DOM事件和JS都是是浏览器的功能,他们是两个平行的功能,并没有从属关系。
JS知识调用了DOM提供的addEventListener这个API而已
- 那么我们要如何写出一个JS事件系统呢? 这个问题留给以后的我