事件
事件模型
事件的本质是程序的各个组成部分之间的一种通信方式,也是异步编程的一种实现,DOM 支持大量的事件。
DOM 节点的事件操作(监听和触发) —— EventTarget 接口
DOM 节点的事件操作(监听和触发),都定义在 EventTarget 接口。
所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如XMLHttpRequest
、AudioNode
、AudioContext
)也部署了这个接口
主要提供三个实例方法:
addEventListener()
:绑定事件的监听函数removeEventListener()
:移除事件的监听函数dispatchEvent()
:触发事件
addEventListener
target.addEventListener(type, listener[, useCapture]);
// type:事件名称,大小写敏感。
// listener:监听函数。事件发生时,会调用该监听函数。
// useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。
// 第三个参数除了布尔值useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。
// capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。
// once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false。
// passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false。
// signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。
element.addEventListener(
"click",
function (event) {
// 只执行一次的代码
},
{ once: true }
);
removeEventListener
div.addEventListener("click", listener, false);
div.removeEventListener("click", listener, false);
dispatchEvent
在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault()
,则返回值为 false,否则为 true
target.dispatchEvent(event);
第一个参数是 Event 对象的实例
para.addEventListener("click", hello, false);
var event = new Event("click");
para.dispatchEvent(event);
监听函数
浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。
事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。
这是事件驱动编程模式(event-driven)的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数:
1、HTML 的on-
属性
on
加上事件名
违反了 HTML 和 JavaScript 代码相分离的原则
<body onload="doSomething()">
<div onclick="console.log('触发事件')"></div>
</body>
2、元素节点的事件属性
同一个事件只能定义一个监听函数,也就是说,如果定义两次
onclick
属性,后一次定义会覆盖前一次。
window.onload = doSomething;
div.onclick = function (event) {
console.log("触发事件");
};
3、EventTarget.addEventListener()
所有的 DOM 节点实例都有
addEventListener
方法,称为该节点定义事件的监听函数。
同一个事件可以添加多个监听函数
能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数
除了 DOM 节点,其他对象(比如 window、XMLHttpRequest 等)也有这个接口,等于是整个 JavaScript 统一的监听函数接口。
window.addEventListener("load", doSomething, false);
this 指向
监听函数内部的 this 指向触发事件的那个元素节点
事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
第一阶段:从
window
对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)第二阶段:在目标节点上触发,称为“目标阶段”(target phase)
第三阶段:从目标节点传导回
window
对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)
<div>
<p>点击</p>
</div>;
var phases = { 1: "capture", 2: "target", 3: "bubble" };
var div = document.querySelector("div");
var p = document.querySelector("p");
div.addEventListener("click", callback, true);
p.addEventListener("click", callback, true);
div.addEventListener("click", callback, false);
p.addEventListener("click", callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
} //点击以后的结果;
// Tag: 'DIV'. EventPhase: 'capture' // Tag: 'P'. EventPhase:("target");
// Tag: 'P'. EventPhase: 'target' // Tag: 'DIV'. EventPhase: 'bubble'
浏览器总是假定 click 事件的目标节点,就是点击位置嵌套最深的那个节点(本例是<div>
节点里面的<p>
节点)。所以,<p>
节点的捕获阶段和冒泡阶段,都会显示为 target 阶段。
事件传播的最上层对象是window
,接着依次是document
,html
(document.documentElement
)和body
(document.body
)。也就是说,上例的事件传播顺序,在捕获阶段依次为window
、document
、html
、body
、div
、p
,在冒泡阶段依次为p
、div
、body
、html
、document
、window
。
事件代理
事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父结点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
var ul = document.querySelector("ul");
ul.addEventListener("click", function (event) {
if (event.target.tagName.toLowerCase() === "li") {
// some code
}
});
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation
方法
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener(
"click",
function (event) {
event.stopPropagation();
},
true
);
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener(
"click",
function (event) {
event.stopPropagation();
},
false
);
stopPropagation
方法只会阻止事件的传播,不会阻止该事件触发<p>
节点的其他click
事件的监听函数。也就是说,不是彻底取消click
事件。
p.addEventListener("click", function (event) {
event.stopPropagation();
console.log(1);
});
p.addEventListener("click", function (event) {
// 会触发
console.log(2);
});
// p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件的传播,不能取消这个事件,因此,第二个监听函数会触发。输出结果会先是1,然后是2。
如果想要彻底取消该事件,不再触发后面所有click
的监听函数,可以使用stopImmediatePropagation
方法
p.addEventListener("click", function (event) {
event.stopImmediatePropagation();
console.log(1);
});
p.addEventListener("click", function (event) {
// 不会被触发
console.log(2);
});
Event 对象
事件发生后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供了一个Event
对象,所有的事件都是这个对象的实例,或者说继承了这个Event.prototype
对象
Event
对象本身就是一个构造函数,可以用来生成新的实例
event = new Event(type, options);
接收两个参数,type
是字符串,表示事件的名称;第二个参数options
是一个对象,表示事件对象的配置。
该配置主要有下面两个属性:
bubbles
:布尔值,可选,默认为 false,表示事件对象是否冒泡cancelable
:布尔值,可选,默认为 false,表示事件是否可以被取消,即能否用Event.preventDefault()
取消这个事件。一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。
实例属性
- Event.eventPhase:返回一个整数常量,表示事件目前所处的阶段,该属性只读
0,事件目前没有发生
1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中
2,事件到达目标节点,即
Event.target
属性指向的那个节点3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中
- Event.currentTaget, Event.target
事件发生以后,会经过捕获和冒泡两个阶段,依次通过多个 DOM 节点。
因此,任意事件都有两个与事件相关的节点,一个是原始触发节点(Event.target),另一个是事件当前正在通过的节点(Event.currentTarget)
前者通常是后者的后代节点
Event.currentTarget 属性返回事件当前所在的节点,即事件当前正在通过的节点,也就是当前正在执行的监听函数所在的节点,随着事件的传播,这个属性的值会变
Event.target 属性返回原始触发事件的那个节点,即事件最初发生的节点,这个属性不会随着事件的传播而改变
- Event.isTrusted:返回布尔值,表示该事件是否由真实的用户行为产生
比如,用户点击链接会产生一个 click 事件,该事件是用户产生的;Event 构造函数生成的事件,则是脚本产生的。
实例方法
Event.preventDefault()
:取消浏览器对当前事件的默认行为。
比如点击链接,浏览器默认会跳转到另一个页面,使用这个方法就不会跳转了
Event.stopPropagation():阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但不包括在当前节点上其他事件监听函数
Event.stopImmediatePropagation():阻止同一个事件的
其他监听函数
被调用,不管监听函数定义在当前节点还是其他节点。Event.composedPath():返回一个数组,成员是事件的最底层节点和依次冒泡经过的上层节点
// HTML 代码如下
// <div>
// <p>Hello</p>
// </div>
var div = document.querySelector("div");
var p = document.querySelector("p");
div.addEventListener(
"click",
function (e) {
console.log(e.composedPath());
},
false
);
// [p, div, body, html, document, Window]
一些事件
鼠标事件
键盘事件
进度事件
表单事件
触摸事件
拖拉事件
其他常见事件
资源事件
session 历史事件
网页状态事件
窗口事件
剪贴板事件
焦点事件
CustomEvent 接口
CustomEvent 接口用于生成自定义事件实例。
浏览器的预定义事件,虽然可以手动生成,但往往不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,就可以使用 CustomEvent 接口生成的自定义事件对象
// 手动定义了build事件
var event = new CustomEvent("build", { detail: "hello" });
function eventHandler(e) {
console.log(e.detail);
}
document.body.addEventListener("build", function (e) {
console.log(e.detail);
});
document.body.dispatchEvent(event);
GlobalEventHandlers 接口
指定事件的回调函数,推荐使用的方法是元素的addEventListener
方法
div.addEventListener("click", clickHandler, false);
除此之外,还有一种方法可以直接指定事件的回调函数
div.onclick = clickHandler;
这个接口由 GlobalEventHandlers 接口提供的。
它的优点是使用比较方便,缺点是只能为每个事件指定一个回调函数,并且无法指定事件触发的阶段。
HTMLElement、Document 和 Window 都继承了这个接口,也就是说,各种 HTML 元素、document 对象、window 对象上面都可以使用 GlobalEventHandlers 接口提供的属性