事件是表示发生了某事的信号。所有 DOM 节点都会生成此类信号(但事件并不局限于 DOM)。
下面列出了一些最常用的 DOM 事件,仅供参考
鼠标事件
click
– 当鼠标点击元素时(触摸屏设备会在轻触时生成该事件)。contextmenu
– 当鼠标右键点击元素时。mouseover
/mouseout
– 当鼠标光标移入 / 移出元素时。mousedown
/mouseup
– 当鼠标按钮在元素上按下 / 释放时。mousemove
– 当鼠标移动时。
键盘事件
keydown
和keyup
– 当键盘键被按下和释放时。
表单元素事件
submit
– 当访问者提交<form>
时。focus
– 当访问者聚焦于一个元素时,例如<input>
。
文档事件
DOMContentLoaded
– 当 HTML 被加载和处理,DOM 完全构建时。
CSS 事件
transitionend
– 当 CSS 动画结束时。
还有许多其他事件。我们将在即将到来的章节中详细介绍特定事件。
事件处理程序
为了对事件做出反应,我们可以分配一个处理程序 – 一个在事件发生时运行的函数。
处理程序是一种在用户操作时运行 JavaScript 代码的方法。
有几种方法可以分配一个处理程序。让我们从最简单的一个开始。
HTML 属性
可以使用名为 on<event>
的属性在 HTML 中设置处理程序。
例如,要为 input
分配 click
处理程序,我们可以使用 onclick
,如下所示
<input value="Click me" onclick="alert('Click!')" type="button">
鼠标单击时,onclick
中的代码运行。
请注意,在 onclick
中我们使用单引号,因为属性本身是用双引号引起来的。如果我们忘记代码在属性中,并在其中使用双引号,如下所示:onclick="alert("Click!")"
,那么它将无法正常工作。
HTML 属性不是编写大量代码的方便之处,所以我们最好创建一个 JavaScript 函数并在那里调用它。
这里单击运行函数 countRabbits()
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Rabbit number " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Count rabbits!">
我们知道,HTML 属性名称不区分大小写,所以 ONCLICK
与 onClick
和 onCLICK
…一样,但通常属性是小写的:onclick
。
DOM 属性
我们可以使用 DOM 属性 on<event>
分配一个处理程序。
例如,elem.onclick
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you');
};
</script>
如果使用 HTML 属性分配处理程序,则浏览器会读取它,从属性内容创建一个新函数并将其写入 DOM 属性。
所以这种方式实际上与前一种方式相同。
这两个代码片段的工作方式相同
-
仅 HTML
<input type="button" onclick="alert('Click!')" value="Button">
-
HTML + JS
<input type="button" id="button" value="Button"> <script> button.onclick = function() { alert('Click!'); }; </script>
在第一个示例中,HTML 属性用于初始化 button.onclick
,而在第二个示例中 – 脚本,这就是全部区别。
由于只有一个 onclick
属性,因此我们不能分配多个事件处理程序。
在下面的示例中,使用 JavaScript 添加处理程序会覆盖现有的处理程序
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
elem.onclick = function() { // overwrites the existing handler
alert('After'); // only this will be shown
};
</script>
要删除处理程序 – 分配 elem.onclick = null
。
访问元素:this
处理程序中 this
的值是元素。具有处理程序的那个元素。
在下面的代码中,button
使用 this.innerHTML
显示其内容
<button onclick="alert(this.innerHTML)">Click me</button>
可能的错误
如果您开始使用事件,请注意一些细微之处。
我们可以将现有函数设置为处理程序
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
但请小心:应将该函数分配为 sayThanks
,而不是 sayThanks()
。
// right
button.onclick = sayThanks;
// wrong
button.onclick = sayThanks();
如果我们添加括号,则 sayThanks()
将变为函数调用。因此,最后一行实际上获取函数执行的结果,即 undefined
(因为函数不返回任何内容),并将其分配给 onclick
。这不起作用。
…另一方面,在标记中,我们确实需要括号
<input type="button" id="button" onclick="sayThanks()">
这种差异很容易解释。当浏览器读取属性时,它会创建一个处理程序函数,其主体来自属性内容。
因此,标记会生成此属性
button.onclick = function() {
sayThanks(); // <-- the attribute content goes here
};
请勿将 setAttribute
用于处理程序。
这样的调用不起作用
// a click on <body> will generate errors,
// because attributes are always strings, function becomes a string
document.body.setAttribute('onclick', function() { alert(1) });
DOM 属性区分大小写。
将处理程序分配给 elem.onclick
,而不是 elem.ONCLICK
,因为 DOM 属性区分大小写。
addEventListener
上述分配处理程序方式的基本问题是,我们无法为一个事件分配多个处理程序。
比方说,我们代码的一部分希望在单击时突出显示按钮,而另一部分希望在同一单击时显示消息。
我们希望为此分配两个事件处理程序。但新的 DOM 属性会覆盖现有的属性
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // replaces the previous handler
网络标准的开发人员很早以前就理解了这一点,并提出了一种使用特殊方法 addEventListener
和 removeEventListener
来管理处理程序的替代方法,不受此类约束的限制。
添加处理程序的语法
element.addEventListener(event, handler, [options]);
event
- 事件名称,例如
"click"
。 handler
- 处理程序函数。
options
- 具有以下属性的附加可选对象
要移除处理程序,请使用 removeEventListener
element.removeEventListener(event, handler, [options]);
要移除一个处理程序,我们应该传递与分配的函数完全相同的一个函数。
这不起作用
elem.addEventListener( "click" , () => alert('Thanks!'));
// ....
elem.removeEventListener( "click", () => alert('Thanks!'));
处理程序不会被移除,因为 removeEventListener
获取另一个函数——具有相同的代码,但这无关紧要,因为它是一个不同的函数对象。
这是正确的方法
function handler() {
alert( 'Thanks!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
请注意——如果我们不将函数存储在变量中,那么我们就无法移除它。没有办法“回读”由 addEventListener
分配的处理程序。
多次调用 addEventListener
允许它添加多个处理程序,如下所示
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Thanks!');
};
function handler2() {
alert('Thanks again!');
}
elem.onclick = () => alert("Hello");
elem.addEventListener("click", handler1); // Thanks!
elem.addEventListener("click", handler2); // Thanks again!
</script>
正如我们在上面的示例中看到的,我们可以使用 DOM 属性和 addEventListener
来设置处理程序。但通常我们只使用其中一种方法。
addEventListener
存在无法通过 DOM 属性分配的事件。只能使用 addEventListener
。
例如,DOMContentLoaded
事件,当文档加载且 DOM 已构建时触发。
// will never run
document.onDOMContentLoaded = function() {
alert("DOM built");
};
// this way it works
document.addEventListener("DOMContentLoaded", function() {
alert("DOM built");
});
因此,addEventListener
更通用。虽然,此类事件是例外,而不是规则。
事件对象
为了正确处理事件,我们希望更多地了解所发生的事情。不仅仅是“点击”或“按下键”,而是指针坐标是什么?按下了哪个键?等等。
当事件发生时,浏览器会创建一个事件对象,将详细信息放入其中,并将其作为参数传递给处理程序。
以下是从事件对象获取指针坐标的示例
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(event) {
// show event type, element and coordinates of the click
alert(event.type + " at " + event.currentTarget);
alert("Coordinates: " + event.clientX + ":" + event.clientY);
};
</script>
event
对象的一些属性
event.type
- 事件类型,此处为
"click"
。 event.currentTarget
- 处理事件的元素。这与
this
完全相同,除非处理程序是箭头函数,或其this
绑定到其他内容,那么我们可以从event.currentTarget
获取元素。 event.clientX
/event.clientY
- 对于指针事件,光标相对于窗口的坐标。
还有更多属性。其中许多取决于事件类型:键盘事件有一组属性,指针事件——另一组,当我们继续了解不同事件的详细信息时,我们稍后将研究它们。
如果我们在 HTML 中分配一个处理程序,我们也可以使用 event
对象,如下所示
<input type="button" onclick="alert(event.type)" value="Event type">
这是可能的,因为当浏览器读取属性时,它会创建一个这样的处理程序:function(event) { alert(event.type) }
。也就是说:它的第一个参数称为 "event"
,主体取自属性。
对象处理程序:handleEvent
我们不仅可以分配一个函数,还可以使用 addEventListener
将一个对象分配为事件处理程序。当事件发生时,将调用其 handleEvent
方法。
例如
<button id="elem">Click me</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
正如我们所看到的,当 addEventListener
接收一个对象作为处理程序时,它会在事件发生时调用 obj.handleEvent(event)
。
我们还可以使用自定义类的对象,如下所示
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
在这里,同一个对象处理这两个事件。请注意,我们需要使用 addEventListener
明确设置要监听的事件。menu
对象在这里仅获取 mousedown
和 mouseup
,而不是任何其他类型的事件。
方法 handleEvent
不必自己完成所有工作。它可以调用其他特定于事件的方法,如下所示
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Mouse button pressed";
}
onMouseup() {
elem.innerHTML += "...and released.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
现在事件处理程序被明确分开,这可能更容易支持。
总结
有 3 种分配事件处理程序的方法
- HTML 属性:
onclick="..."
。 - DOM 属性:
elem.onclick = function
。 - 方法:
elem.addEventListener(event, handler[, phase])
用于添加,removeEventListener
用于移除。
HTML 属性很少使用,因为 HTML 标签中间的 JavaScript 看起来有点奇怪和陌生。而且也不能在其中编写大量代码。
DOM 属性可以使用,但我们不能为特定事件分配多个处理程序。在许多情况下,这种限制并不紧迫。
最后一种方法是最灵活的,但也是最长的。有一些事件只能使用它,例如 transitionend
和 DOMContentLoaded
(待介绍)。此外,addEventListener
支持将对象作为事件处理程序。在这种情况下,将在事件发生时调用方法 handleEvent
。
无论如何分配处理程序,它都会将事件对象作为第一个参数获取。该对象包含有关所发生事件的详细信息。
我们将在下一章中详细了解事件以及不同类型的事件。
评论
<code>
标记,对于多行,请将其包装在<pre>
标记中,对于 10 行以上,请使用沙盒 (plnkr、jsbin、codepen…)