所有的js库或框架都提供快捷DOM事件绑定方法,比如JQuery中:
$("#target").click((() => console.log("clicked"));
以及angular中的
ng-click="click(xxx)"
但这背后是什么,有些了解,但比较模糊,今天就详细了解一下。
原生js通常有2种方法给DOM添加事件: on-event handler 以及 EventTarget.addEventListener()
on-event handler
这是比较古老的一种方式,可以直接在HTML元素上添加on-event处理,比如onclick;也可以通过js添加,参见下面的例子:
注意,这两种方式的效果是不完全相同的,具体表现在this
的值上:直接写下在HTML元素上的方法,this
指向的是它注册的那个DOM元素;而通过js添加的情况,this
在非严格模式下指向的是window
,严格模式下是undefined
。
EventTarget.addEventListener()
on-event方法一个事件只能添加一个handler,当然,可以在这个handler里再调用别的handler来达到注册多个handler的效果;EventTarget.addEventListener()是更现代的方法,可以添加多个handler(listener), JQuery使用的就是这种方法:
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
其中,
- type: 表示事件类型,详细的事件类型可以参考这里,这里记录几种常见的event:
- DOMContentLoaded: 原始HTML文档载入完成,这个时候stylesheets, images, subframes可能还没有完成,但对于scripts,情况有些复杂,简单来说,在该事件发生中,同步脚本已经加载并执行完,但异步脚本可能还没有。
- load: 所有资源以及它们的依赖资源都完成加载,所以通常情况下,load事件要晚于DOMContentLoaded
- focus, blur
- resize, scroll
- keydown, keyup, keypress
- mouseenter, mouseover, mousemove, mousedown, mouseup, click, dblclick, wheel, mouseleave, mouseout
- listener: 可以是js方法或者实现了Event接口的对象
- options: 可以有以下几个属性
- capture: boolean, 详见下面的useCapture
- once: boolean, 表示该事件是不是只被触发一次,如果设成true,那么被触发后,listener将被移除
- passive: boolean, 表示该listener会不会调用
preventDefault()
;preventDefault()
表示阻止浏览器展示控件的默认行为,比如checkbox对于click的默认行为是选中或取消选中,如果在选中事件中调用了preventDefault()
,那么控件将不会被选中;可以用来阻止用户进行非法操作;passive还有一个作用,就是提高页面滚动的性能,详细请看这里
- useCapture: useCapture表示是不是需要在Capture Phase捕获事件,关于Capture Phase详见后面的解释,默认为false.
W3C标准中event触发有3个阶段:要理解useCapture的含义,先要理解事件发生的3个阶段:
- Capture Phase: 自顶向下,即从window到event target,查看路径上的每个元素是否定义了handler;
- Target Phase: 又叫on-target phase,即自顶向下找到event target时;
- Bubble Phase: 自底向上,即从event target到window,查看路径上的每个元素是否定义了handler. 为什么要分3个阶段呢?这要得从浏览器的历史说起,简单来说,在很久以前,世上只有2个浏览器的时候,其中一个采用的是capture模式,即事件是自顶向下传递的;而另一个采用的是bubble模式,事件从当前的target开始向下冒泡;而W3C为了兼容这2种模式,就采用了折衷的办法,定义了事件的3个阶段。
默认情况下浏览器采用的是bubble模式,useCapture是false,请看下面的例子:
不难猜中结果:孩子结点的click先触发,然后冒泡到父结点,触发父结点的click事件。 可以把useCapture改成true看看结果是什么。
这里再多说一句,如果我们只想触发孩子结点的click事件,而不想冒泡到父结点怎么办?用event.stopPropagation()
, event.stopPropagation()
将阻止事件冒泡(但Capture Phase还是会发生)。