DOM事件与事件委托(未完结)

351 阅读4分钟

今天我学习了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

  • 综上我们发现事件委托的优点有:
  1. 省监听内存
  2. 可以监听动态元素

封装事件委托

接如上代码我们继续写到

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事件系统呢? 这个问题留给以后的我