DOM事件委托

90 阅读6分钟

target和currentTarget

代码:

                       // 当用户点击文字的时候
<div>                  // e.currentTarget就是div
  <span>文字</span>    // e.target就是span
</div>
复制代码
  • e.target: 用户操作的元素
  • e.currentTarget: 程序员监听的元素
  • thise.currentTarget, 是不推荐的用法,因为this指向不确定

特例: 在同级别div

div.addEventListener('click', f1)  // f1先被执行
div.addEventListener('click', f2, true) // 然后被f2执行
复制代码

请问, f1还是f2先执行?如果把代码调换顺序,哪个先被执行?

答案:谁先事件监听,谁就先执行。但是这是一个特例。

特例存在的情况:

  • 在不考虑父子同时被监听的情况下,只有一个div被监听
  • fn会分别在捕获阶段和冒泡阶段监听click事件
  • 开发者监听的元素就是用户点击的元素

事件绑定

  1. 直接获取元素绑定

优点是:简单和稳定,可以确保它在你使用的不同浏览器中运作一致。

缺点:只会在事件冒泡中运行;一个元素一次只能绑定一个事件处理函数,新绑定的事件处理函数会覆盖旧的事件处理函数;事件对象参数(e)只有在非IE浏览器才可用

element.onclick = function(e){
        // ...
    };
复制代码
  1. 直接在元素里面使用事件属性
<button οnclick="f1"></button>
复制代码

3、添加事件监听

w3c方法

优点:该方法同时支持事件处理的捕获和冒泡阶段;事件阶段取决于addEventListener最后的参数设置:false (冒泡) 或 true (捕获);在事件处理函数内部。事件对象总是可以通过处理函数的第一个参数(e)捕获;可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件

缺点:IE不支持,你必须使用IEattachEvent函数替代

element.addEventListener('click', function(e){
        // ...
    }, false);
复制代码

IE方法

优点:可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。

缺点:IE仅支持事件捕获的冒泡阶段;如果使用了this,事件监听函数内的this关键字指向了window对象,而不是当前元素, 事件对象仅存在与window.event参数中;事件必须以ontype的形式命名,比如,onclick而非click;仅IE可用,你必须在非IE浏览器中使用W3CaddEventListener

注意:不是意味着版本的IE没有事件捕获,它也是先发生事件捕获,再发生事件冒泡,只不过这个过程无法通过程序控制。

element.attachEvent('onclick', function(){
        // ...
});
复制代码

取消事件绑定

  • 使用removeEventListener
  • 使用detachEvent
// W3C
element.removeEventListener('click', function(e){
        // ...
    }, false);

// IE
element.detachEvent('onclick', function(){
        // ...
});
复制代码

取消冒泡

  • 捕获不可以被取消,冒泡可以被取消
  • 在支持addEventListener()的浏览器中,可以调用事件对象的stopPropagation()方法以阻止事件的继续传播。如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用stopPropagation()之后任何其他对象上的事件处理程序将不会被调用。即可以阻止事件在冒泡阶段的传播
  • IE 9之前的IE不支持stopPropagation()方法,而是设置事件对象cancelBubble属性为true来实现阻止事件进一步传播
  • 有些事件不可以取消冒泡,例如MDN搜索scroll event, 看到BubblesCanceleble, 在Canceleble可以找到不可以取消冒泡的事件
// w3c
element.addEventListener("click", function(e){
    // 在捕获阶段阻止事件的传播
    e.stopPropagation();
}, true);

复制代码

阻止滚动

  • scroll事件为不可取消的冒泡事件,阻止scroll默认动作没有用,因为先有滚动才有滚动事件。要阻止滚动,需要阻止wheeltouchstart的默认动作
  • CSS上使用overflow:hidden可以直接取消滚动条,然而JS上依然可以修改scrollTop
element.addEventListener('wheel',(e)=>{
    e.preventDefault() // 取消滚轮的默认动作
})

element.addEventListener('touchstart',(e)=>{
    e.preventDefault() // 取消手机上的触屏默认动作
})
复制代码

取消默认行为

  • e.preventDefault()可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为
  • IE 9之前的IE中,可以通过设置事件对象的returnValue属性为false达到同样的效果
function cancelHandler(event){
    var event=event||window.event;//兼容IE
    
    //取消事件相关的默认行为
    if(event.preventDefault)    //标准技术
        event.preventDefault();
    if(event.returnValue)    //兼容IE9之前的IE
        event.returnValue=false;
    return false;    //用于处理使用对象属性注册的处理程序
}
复制代码

自定义事件

浏览器自带事件,一共有100多种事件,可以在MDN上查询。同时,开发者也可以在自带事件之外,自定义一个事件

// html代码
<body>
  <div id=div1>
    <button id=button1>点击触发事件     
    </button>
  </div>
**************
// js代码

// 先自定义事件,然后点击时触发自定义事件
button1.addEventListener('click', ()=>{
  const event = new CustomEvent("tab", {"detail":{name:'tab', age: 18}},
  bubbles: true) // 允许冒泡
  button1.dispatchEvent(event)
})

// 监听自定义事件
button1.addEventListener('tab', (e)=>{
  console.log('tab')
  console.log(e)
})
复制代码

事件委托

定义

  • JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
  • 对事件处理程序过多问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序
  • 优点:提高页面性能,可以监听动态元素

事件委托使用场景

  1. 实例一:假设给100个按钮添加点击事件
// html代码
<div id="div1">
 <button data-id="1">1</button>
 <button data-id="2">2</button>
 <button data-id="3">3</button>
 <button data-id="4">4</button>
 ****************
</div>

// js代码
div1.addEventListener('click', (e)=>{
    const t = e.target
    if(t.tagName.toLowerCase() === 'button'){
        console.log('button'被点击了)
        console.log('button'内容是 + t.textContext) // 获取被点击元素的文本内容
        console.log('button 的data-id是:'+ t.dataset.id) // 获取被点击元素的dataset.id
    }
})
复制代码
  1. 实例二:监听目前不存在的元素的点击事件,例如下面的例子中1秒钟之后button才出现
// html代码
<div id="div1">

</div>

// js代码
setTimeout(()=>{
    const button = document.createElement('button')
    button.textContent= 'click 1'
    div1.appendChild(button)
},1000)

div1.addEventListener('click', (e)=>{
    const t = e.target
    if(t.tagName.toLowerCase() === 'button'){
        console.log('button'被点击了)
    }
})
复制代码

封装事件委托

写出一个函数,例如on('click', '#div1','li', fn),当用户点击div1中的li时,调用fn函数

// 答案一
setTimeout(()=>{
    const button = document.createElement('button')
    button.textContent= 'click 1'
    div1.appendChild(button)
},1000)

functin 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)){
            fn(e)
        }
    })
}

on('click', '#div1', 'button',()=>{
    console.log('button 被点击了')
})
复制代码
// 答案二: 使用递归进行判断
function on(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) {
          el = null
          break
        }
        el = el.parentNode
      }
      el && fn.call(el, e, el)
    })
    return element
复制代码

注意的是:本章节讲的是DOM的事件,JS只是调用了DOM提供的addEventListener方法,其实JS不支持事件

参考

DOM事件与事件委托