我们不仅可以分配处理程序,还可以从 JavaScript 生成事件。
自定义事件可用于创建“图形组件”。例如,我们自己的基于 JS 的菜单的根元素可能会触发事件,告诉菜单发生了什么:open
(菜单打开)、select
(选择了一个项目)等等。另一段代码可能会侦听这些事件并观察菜单发生了什么。
我们不仅可以生成我们自己发明的全新事件,还可以生成内置事件,例如 click
、mousedown
等。这可能有助于自动化测试。
事件构造函数
内置事件类形成一个层次结构,类似于 DOM 元素类。根是内置 Event 类。
我们可以这样创建 Event
对象
let event = new Event(type[, options]);
参数
-
type – 事件类型,一个字符串,如
"click"
或我们自己的"my-event"
。 -
options – 带有两个可选属性的对象
bubbles: true/false
– 如果为true
,则事件冒泡。cancelable: true/false
– 如果为true
,则可以阻止“默认操作”。稍后我们将了解它对自定义事件的含义。
默认情况下,两者都为 false:
{bubbles: false, cancelable: false}
。
dispatchEvent
在创建事件对象后,我们应该使用调用 elem.dispatchEvent(event)
在元素上“运行”它。
然后,处理程序会对其做出反应,就像它是一个常规浏览器事件一样。如果使用 bubbles
标志创建了事件,则它会冒泡。
在下面的示例中,click
事件是在 JavaScript 中启动的。处理程序的工作方式与单击按钮相同
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
有一种方法可以将“真实”用户事件与脚本生成事件区分开来。
对于来自真实用户操作的事件,属性 event.isTrusted
为 true
,对于脚本生成的事件,则为 false
。
冒泡示例
我们可以创建一个名为 "hello"
的冒泡事件,并在 document
上捕获它。
我们只需要将 bubbles
设置为 true
<h1 id="elem">Hello from the script!</h1>
<script>
// catch on document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// ...dispatch on elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// the handler on document will activate and display the message.
</script>
注释
- 我们应该对自定义事件使用
addEventListener
,因为on<event>
仅对内置事件存在,document.onhello
不起作用。 - 必须设置
bubbles:true
,否则事件不会冒泡。
内置(click
)和自定义(hello
)事件的冒泡机制相同。还有捕获和冒泡阶段。
MouseEvent、KeyboardEvent 等
以下是从 UI 事件规范 中获取的 UI 事件类的简短列表
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent
- …
如果我们想要创建此类事件,我们应该使用它们而不是 new Event
。例如,new MouseEvent("click")
。
正确的构造函数允许为该类型的事件指定标准属性。
例如,鼠标事件的 clientX/clientY
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
请注意:通用 Event
构造函数不允许这样做。
让我们尝试一下
let event = new Event("click", {
bubbles: true, // only bubbles and cancelable
cancelable: true, // work in the Event constructor
clientX: 100,
clientY: 100
});
alert(event.clientX); // undefined, the unknown property is ignored!
从技术上讲,我们可以在创建后直接分配 event.clientX=100
来解决此问题。因此,这是一个方便性和遵循规则的问题。浏览器生成的事件始终具有正确的类型。
不同 UI 事件的属性完整列表在规范中,例如,MouseEvent。
自定义事件
对于我们自己的全新事件类型,如 "hello"
,我们应该使用 new CustomEvent
。从技术上讲,CustomEvent 与 Event
相同,但有一个例外。
在第二个参数(对象)中,我们可以添加一个附加属性 detail
,用于我们希望随事件传递的任何自定义信息。
例如
<h1 id="elem">Hello for John!</h1>
<script>
// additional details come with the event to the handler
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "John" }
}));
</script>
detail
属性可以包含任何数据。从技术上讲,我们可以在不使用它的情况下生存,因为我们可以在创建常规 new Event
对象后向其中分配任何属性。但是,CustomEvent
为其提供了特殊的 detail
字段,以避免与其他事件属性发生冲突。
此外,事件类描述了“事件的类型”,如果事件是自定义的,那么我们应该使用 CustomEvent
来明确说明事件的类型。
event.preventDefault()
许多浏览器事件都有“默认操作”,例如导航到链接、开始选择等。
对于新的自定义事件,肯定没有默认的浏览器操作,但是分派此类事件的代码可能在其自己的计划中规定了在触发事件后要执行的操作。
通过调用 event.preventDefault()
,事件处理程序可以发送一个信号,表明这些操作应该被取消。
在这种情况下,对 elem.dispatchEvent(event)
的调用返回 false
。分派该事件的代码知道它不应该继续。
让我们看一个实际的例子——一只躲藏的兔子(可能是关闭菜单或其他东西)。
在下面,你可以看到一个 #rabbit
和 hide()
函数,它在上面分派 "hide"
事件,以让所有相关方知道兔子将要躲藏起来。
任何处理程序都可以使用 rabbit.addEventListener('hide',...)
侦听该事件,并在需要时使用 event.preventDefault()
取消操作。然后,兔子就不会消失
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
function hide() {
let event = new CustomEvent("hide", {
cancelable: true // without that flag preventDefault doesn't work
});
if (!rabbit.dispatchEvent(event)) {
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Call preventDefault?")) {
event.preventDefault();
}
});
</script>
请注意:事件必须带有标志 cancelable: true
,否则将忽略 event.preventDefault()
调用。
事件中的事件是同步的
通常,事件会在队列中处理。也就是说:如果浏览器正在处理 onclick
,并且发生了新事件,例如鼠标移动,那么它的处理就会排队,相应的 mousemove
处理程序将在 onclick
处理完成后被调用。
值得注意的例外情况是,当一个事件在另一个事件内部被启动时,例如使用 dispatchEvent
。此类事件会立即处理:调用新的事件处理程序,然后恢复当前事件处理。
例如,在下面的代码中,menu-open
事件在 onclick
期间被触发。
它会立即处理,无需等待 onclick
处理程序结束
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// triggers between 1 and 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
输出顺序为:1 → 嵌套 → 2。
请注意,嵌套事件 menu-open
在 document
上被捕获。嵌套事件的传播和处理在处理返回到外部代码(onclick
)之前完成。
这不仅与 dispatchEvent
有关,还有其他情况。如果事件处理程序调用触发其他事件的方法,它们也会以嵌套方式同步处理。
假设我们不喜欢这种情况。我们希望 onclick
首先得到完全处理,独立于 menu-open
或任何其他嵌套事件。
然后,我们可以将 dispatchEvent
(或另一个触发事件的调用)放在 onclick
的末尾,或者,可能更好,将其包装在零延迟 setTimeout
中
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('nested'));
</script>
现在,dispatchEvent
在当前代码执行(包括 menu.onclick
)完成后异步运行,因此事件处理程序完全独立。
输出顺序变为:1 → 2 → 嵌套。
总结
要从代码生成事件,我们首先需要创建一个事件对象。
通用 Event(name, options)
构造函数接受任意事件名称和具有两个属性的 options
对象
- 如果事件应该冒泡,则
bubbles: true
。 - 如果
event.preventDefault()
应该起作用,则cancelable: true
。
本机事件的其他构造函数,如 MouseEvent
、KeyboardEvent
等,接受特定于该事件类型的属性。例如,鼠标事件的 clientX
。
对于自定义事件,我们应该使用 CustomEvent
构造函数。它有一个名为 detail
的附加选项,我们应该将事件特定数据分配给它。然后,所有处理程序都可以通过 event.detail
访问它。
尽管有生成浏览器事件(如 click
或 keydown
)的技术可能性,但我们应该非常谨慎地使用它们。
我们不应该生成浏览器事件,因为这是一种运行处理程序的非常规方法。在大多数情况下,这是一种糟糕的架构。
本机事件可能会生成
- 作为一种非常规方法,如果第三方库不提供其他交互方式,则可以使其按所需方式工作。
- 对于自动化测试,在脚本中“单击按钮”并查看界面是否正确响应。
具有我们自己的名称的自定义事件通常出于架构目的而生成,以表示我们的菜单、滑块、旋转木马等内部发生的情况。
评论
<code>
标记,对于多行代码 – 将其包装在<pre>
标记中,对于 10 行以上的代码 – 使用沙盒 (plnkr、jsbin、codepen…)