Better

Ethan的博客,欢迎访问交流

从一次事件失效谈 JS 事件模型

最近碰到一个 scroll 事件监听失效的问题,在项目中图片懒加载是基于监听 window scroll 事件实现的,在一个页面布局中,使用了子容器来实现局部滚动效果,却发现 window scroll 事件不再触发了,当初没有过多思考,如今发现,这里面是有点小文章的

事件模型

首先我们需要明白的事,JavaScript事件有两个阶段 ——(1)捕获阶段(Capture Phase)(2)冒泡阶段(Bubble Phase)。捕获阶段是事件从 window 到传递到目标元素的过程,而冒泡阶段是事件从目标元素传递到 window 的过程。

在实际编码中,我们最多使用的是事件冒泡,就连 addEventListener 的第三个参数模式就是 false,表示使用事件冒泡的监听方式。至于为什么较少使用捕获,我想和早期IE的兼容性有关吧。就需要了解下历史了。

JS 事件流最早要从 IE 和网景公司的浏览器大战说起,IE 提出的是冒泡流,而网景提出的是捕获流,后来在W3C 组织的统一之下,JS 支持了冒泡流和捕获流,但是目前低版本的 IE 浏览器还是只能支持冒泡流(IE6,IE7,IE8 均只支持冒泡流),所以为了能够兼容更多的浏览器,建议大家使用冒泡流。

特殊的 scroll

查询 MDN,发现有这么一句话

element 的 scroll 事件不冒泡, 但是 document 的 defaultView 的 scroll 事件冒泡

这样一来问题就很好解释了,如果 scroll 的目标元素是一个元素的话,也就只有从 window 到目标的捕获阶段以及目标的冒泡阶段,此时事件不再冒泡。因此尝试在父级监视 scroll 的冒泡阶段是无效的。如果 scroll 是由 document.defaultView(目前 document 关联的window对象)产生的有冒泡阶段。但是由于其本身就是 DOM 树里最顶级的对象,因此只能在 window 里监视 scroll 的捕获阶段以及冒泡阶段。

事件区别

事件的发展主要有 DOM0 和 DOM2

  • DOM Level 2 可以在一个元素上面注册多个事件,但是 DOM Level 0 就不可以
  • DOM Level 0 的兼容性好,可以支持所有的浏览器,但是 DOM Level 2 中的 addEventListener 的这个方法在 IE 浏览器是不支持的,IE浏览器(主要是<IE9)支持 attachEvent 事件,attachEvent 事件支持两个参数,第一个是事件类型,第二个是执行的函数,DOM Level 0 不同于 addEventListener,这个在使用的时候要加上 on,例如:addEventListener 的单击事件是 click,而 attachEvent 的单击事件是 onclick,由于 IE 只支持冒泡事件,所以没有第三个参数
  • DOM Level 2 在 IE 中的绑定事件是 attachEvent,解除绑定是 detachEvent,在标准的浏览器绑定事件是 addEventListener,解除绑定是 removeEventListener

关于阻止冒泡

event.stopPropagation() 的误解

  • 由于事件分为冒泡阶段和捕获阶段,很容易误解成只能用于冒泡阶段,这是错误的,捕获阶段也可以使用,建议理解成向上/向下冒泡
  • 正确定义阻止捕获和冒泡阶段中当前事件的进一步传播

兼容性

阻止默认行为和阻止冒泡

  • 标准浏览器:stopPropagation() 与 preventDefault()
  • 低版本IE浏览器:window.event.returnValue=false与event.cancelBubble=true
var preventDeafult = function(event) {
    var event = getEvent(event)
    if(event.preventDeafult) {
        event.preventDeafult()
    } else {
        event.returnValue = false
    }
}

获取事件对象

var getEvent = function(event) {
    return event || window.event
}

获得事件源

var getTarget = function(event) {
    var event = getEvent(event)
    // 标准浏览器target,IE下srcElement
    return event.target || event.srcElement
}

有点坑的 currentTarget

更新事件:20200708

今天有需求需要知道绑定事件的元素,但一时忘记了属性叫什么名字,将 MouseEvent 对象打印出来,却始终找不到合适的内容。当查资料得知属性名叫 currentTarget 时,log 中却是 null 值。

实际上,currentTarget 实际上是不可能为 null,log 中是 null 的原因是因为 log 保存的是引用,事件冒泡结束后,该值会被改成 null。

参考



留言