22. DOM事件与事件委托

228 阅读3分钟

22.1 事件捕获和事件冒泡

起源是啥?

看源码:

<div id="father"><button id="child"></button></div>

button是儿子, 被div爸爸包裹着.

问题来了?: 如果我点击了button, 我是先响应div爸爸的事件, 还是先响应button儿子事件呢?

微软就说: 我觉得应该先响应儿子, 再响应爸爸, 称之为事件冒泡

网景就说: 老子看不惯微软, 我要反着来: 称之为事件捕获

两人就闹到了w3c.

w3c作为和事佬就说: 浏览器应该支持两种顺序, 首先按照事件捕获顺序看有没有事件监听, 再按照事件冒泡.


那岂不是所有函数都要执行两遍?

不是的, 事件捕获, 和事件冒泡就像两个独立的容器, 开发者自由选择将事件放在哪个容器.

这里说明w3c有个小心眼, 偷偷的赞同微软的事件冒泡: w3c的api 默认将函数放到事件冒泡的容器, 除非开发者将那个bool参数设置为true

baba.addEventListener('click', fn, bool)


可以在setTimeout中调用事件e吗?

不可以, 因为在语句执行完的时候, e会消亡(具体来说,是会被浏览器偷偷修改,修改的边界条件很复杂). 所以我们可以将e中需要的信息保存下来


target和currentTarget的区别?

e.target: 用户操作的元素

e.currentTarget: 被程序员监听的函数.

例如:

<div id="father" onClick = {监听事件}>
    <button id="child">请点击这个按钮</button>
</div>

button被div包裹着, div被程序员监听着, button就是e.target, div就是e.currentTarget


事件捕获和事件冒泡可不可以取消?

捕获不可取消, 冒泡可以取消

可以设置.e.stopPropagation. 为true. 当一个独立组件说: 出了什么问题找我就行, 不要告诉我爸爸


有哪些时间不可取消冒泡

例如滚动scroll不可取消冒泡, 其他的可以在mdn中查看

如果非要阻止滚动:

  1. 阻止滚轮
  2. 然后隐藏滚动条
  3. 禁止触屏

22.2 Dom事件

浏览器有100多个事件, 在mdn中有列表, 还可以自定义事件

如何自定义事件?

button1.addEventListener('click',()=>{
  const event = new CustomEvent('frank',{
    detail: {name:'frank', age:18},
    bubble:true, //是否会向上冒泡, 如何false,就不会告诉爸爸
    cancelable:false; //是否可以取消冒泡
  })
  button1.dispatchEvent(event)
})

div1.addEventListener('frank', (e)=>{
  console.log("frank 事件触发了")
  console.log(e.detail)
})

22.3 事件委托

简单来说:委托别的元素帮我监听.


什么时候会使用事件委托?

两种情况下使用:

  • 要给100个按钮添加事件,对包裹的元素增加监听: 省内存

  • 我想监听不存在的元素: 我先监听祖先: 监听动态事件

matchs: 一个元素是不是button


如何实现事件委托?

//element: 事件委托的元素, 也就是被监听的爸爸, 如div
//selector: 用户操作的元素, 也就是被操作的儿子, 如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 就是判断是某个元素吗
      const t = e.target
    }
  })
}

上面实现的事件监听会有什么问题?

button里面加一个孩子span, 点击无效. 因为改变了e.target了,但是没有改变selector 解决方案:

function on(eventType, element, selector, fn){
  if(!(element instanceof Element)){
    element = document.querySelector(element)
  }
  element.addEventListener(eventType, (e)=>{
    const el=e.target
    while(!el.matches(selector)){
      if (element === el){  //如果找到div,也就是e.currentTarget都还没有找到button,那就放弃吧
        el = null
        break
      }
      el  = el.parentNode//当前e.target不对,那就一直往上找祖先,有没有button
    }
    el && fn.call(el, e, el)
  })
  return element
}

js支持Dom事件吗?

js不支持事件, Dom事件只是浏览器提供的功能. js只是调用了Dom的addEventListener