前端小贴士 - 事件流与事件处理程序

一、什么是事件流?

  事件流描述的是页面中接受事件的顺序。IE的事件流是事件冒泡流,Netscape的事件流是事件捕获流。

  DOM2级事件中规定了事件流包括:事件捕获阶段、处于目标阶段和事件冒泡阶段。

在这里插入图片描述

二、DOM0级事件处理程序

  DOM0级事件主要通过on+事件名称(小写字母)的方式指定事件处理程序,例如:

  const el = document.getElementById('js-btn')
  el.onclick = function () {
    console.log('clicked')
  }

  这种方式具有跨浏览器的优势,但是对于任意事件只能指定一个事件处理程序。

  通过将事件处理程序置为null的方式删除该事件的事件处理程序:

  el.onclick = null

三、DOM2级事件处理程序

  DOM2级事件定义了两个方法:

  • addEventListener(name, callback, useCapture)注册事件处理程序,相比较DOM0,可以注册多个事件处理程序。
  • removeEventListener(name, callback, useCapture)移除指定事件处理程序。
  const el = document.getElementById('js-btn')
  function handleClick () {
    // TODO
  }
  el.addEventListener('click', handleClick, false)
  el.removeEventListener('click', handleClick, false)

  特别需要注意调用removeEventListener()方法时,必须传入具体的函数名。如果事件处理程序为匿名函数,则无法移除。

四、IE中的事件处理程序

  IE实现了类似DOM2级中的两个方法:

  • attachEvent(name, callback),注册事件处理程序;
  • detachEvent(name, callback),移除事件处理程序。

  记住是类似的两个方法,下面是它与DOM2级事件处理程序的差异性:

  • IE传入的name必须是on+事件名(小写字母),DOM2中则是直接为事件名;
  • IE不支持useCapture参数,所以事件处理程序都是注册在事件冒泡阶段;
  • IE事件执行的顺序与注册的顺序是相反的,DOM2中则是相同的;
  • IE事件对象作为window对象的一个属性存在,DOM2中则是浏览器在事件执行的时候传入;
  • IE事件目标通过event.srcElement获取,DOM2中通过event.target获取;
  • IE通过returnValue属性阻止默认事件行为,DOM2中通过preventDefault方法;
  • IE通过cancelBubble属性阻止事件冒泡,DOM2中通过stopPropagation方法。

五、跨浏览器的事件处理

  为了以跨浏览器的方式处理事件,需要对方法和属性进行检测,封装一个统一的API:

const EventUtil = {
  addHandler (el, type, handle) {
    if (el.addEventListener) {
      el.addEventListener(type, handle, false) // 采用普遍支持的冒泡处理
    } else if (el.attachEvent) {
      el.attachEvent(`on${type}`, handle)
    } else {
      el[`on${type}`] = handle // 如果两种方式都不支持,只能采用DOM0级事件处理
    }
  },
  removeHandle (el, type, handle) {
    if (el.removeEventListener) {
      el.removeEventListener(type, handle, false)
    } else if (el.detachEvent) {
      el.detachEvent(`on${type}`, handle)
    } else {
      el[`on${type}`] = null
    }
  },
  getEvent (event) {
    return event ? event.target : window.event
  },
  getTarget (event) {
    return event.target || event.srcElement
  },
  preventDefault (event) {
    if (event.preventDefault) {
      event.preventDefault()
    } else {
      event.returnValue = false
    }
  },
  stopPropagation (event) {
    if (event.stopPropagation) {
      event.stopPropagation()
    } else {
      event.cancelBubble = true
    }
  }
}

  仔细观察上述代码,会发现每一次注册事件处理程序时,都需要对浏览器特性进行嗅探,这样是不是有点多余。

  这里可以采用惰性加载函数的方式,使得初次注册事件处理程序时嗅探浏览器特性,后续不再执行重复的操作:

const EventUtil = {
  addHandler (el, type, handler) {
    if (el.addEventListener) {
      EventUtil.addHandler = function (el, type, handler) {
        return el.addEventListener(type, handler, false)
      }
    } else if (el.attachEvent) {
      EventUtil.addHandler = function (el, type, handler) {
        return el.attachEvent('on' + type, handler)
      }
    } else {
      EventUtil.addHandler = function (el, type, handler) {
        return el['on' + type] = handler
      }
    }
    EventUtil.addHandler.call(null, el, type, handler)
  }
}

六、总结

  在前端开发的过程中,少不了与事件处理程序打交道,那么理解DOM事件流和能够写出兼容性的事件处理程序代码是必不可少的。

参考资料
  • 《JavaScript高级程序设计(第三版)》

  
  ----------------------------

关注「漫谈大前端」
不断成长为一名优秀的前端开发工程师

  ----------------------------
  

  您还可以在这些地方找到我:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值