1、JavaScript 的事件
JavaScript 中的事件共分为鼠标事件、键盘事件、HTML 事件三大类。
1.1、鼠标事件
鼠标事件,就是需要通过鼠标进行触发的事件。
事件 | 发生时间 |
---|---|
onclick | 用户单击对象时 |
ondblclick | 用户双击对象时 |
onmouseover | 鼠标移到某个元素之上时 |
onmouseout | 鼠标移出某个元素时 |
onmousemove | 鼠标被移动 |
onmousedown | 鼠标按键被按下 |
onmouseup | 鼠标按键被松开 |
1.2、键盘事件
键盘事件是指通过按下键盘按键所触发的事件。按下一个按键并抬起的过程,实际上可以分为三个阶段,每个阶段的触发时间如下表所示。
事件 | 发生时间 |
---|---|
onkeydown | 键盘按下去触发 |
onkeypress | 键盘按下并松开的瞬间触发 |
onkeyup | 键盘抬起时触发 |
1.键盘事件注意事项
(1)三个事件的执行顺序
键盘事件一共有三种类型。这三种类型是按顺序执行的,依次是 onkeydown、onkeypress、onkeyup。
(2)长按时触发的事件
当长按一个按键时,会不断触发 onkeydown 和 onkeypress,只有按键抬起以后才会触发 onkeyup 事件。
(3)onkeydown / onkeyup 和 onkeypress 的区别
- onkeypress 只能捕获字母、数字、符号键,不能捕获功能键(如 Enter 键、F1~F12 键等);onkeydown / onkeyup 基本可以捕获所有功能键(特殊键盘的某些按键除外)。
- 捕获字母键时,onkeypress 可以区分大小写,onkeydown 和 onkeyup 不区分大小写。
- 捕获数组键时,onkeydown / onkeyup 可以区分主键盘和小键盘,onkeypress 不能够区分。
(4)通常将键盘事件声明在 document 上
在使用键盘事件时,通常会直接将键盘事件监测到 document 上,而且 onkeydown 和 onkeyup 通常监测一个即可。
代码示例如下:
document.onkeydown = function() {
// 键盘按键按下时触发的函数
}
2.判断键盘按键
在使用键盘事件时,除了需要检测触发的是 onkeydown 还是 onkeyup,更重要的是判断用户按下去的是哪一个按键。
当监测键盘事件时,浏览器会默认将事件因子 e 传入事件触发函数中,事件因子可以通过 keyCode 等属性确认按键 ASCII 码值,进而确定用户按下的是哪一个按键。
判断浏览器按键的第一步是取到事件因子,绝大部分浏览器可以将事件因子通过触发函数传入,但是部分浏览器需要通过 window.event 手动取到。所以,通常使用如下代码兼容所有浏览器。
document.onkeydown = function(e) {
var evn = e || window.event;
}
取到事件因子后,可以通过事件因子取到用户按键的 ASCII 码值。最常用的方式是 evn.keyCode,但是为了兼容所有浏览器,通常采用如下写法。
var code = evn.keyCode || evn.which || evn.charCode;
以 Enter 键为例,可以判断用户按键是否为 Enter 键。
代码示例如下:
document.onkeydown = function(e) {
var evn = e || window.event;
var code = evn.keyCode || evn.which || evn.charCode;
if (code == 13) {
alert("您按下了 Enter 键");
}
}
ASCII 码值 | 按键或含义 |
---|---|
0 | 空字符(Null) |
13 | Enter 键 |
27 | Esc 键 |
32 | 空格键 |
48~57 | 数字键 0~9 |
65~90 | 大写字母 A~Z |
97~122 | 小写字母 a~z |
127 | Delete 键 |
1.3、HTML 事件
事件 | 发生时间 |
---|---|
onload | 文档或图像加载后 |
onunload | 文档卸载后,即退出页面时 |
onblur | 元素失去焦点 |
onselect | 文本被选中 |
oninput | 元素在用户输入时触发 |
onchange | 内容被改变 |
onfocus | 元素获得焦点时 |
onsubmit | 表单提交时 |
onreset | 重置按钮被单击 |
onresize | 窗口被重新调整大小时 |
onscroll | 当文档被滚动时发生的事件 |
ondrag | 当元素正在拖动时触发的事件 |
ondragstart | 当元素开始被拖动的时候触发的事件 |
ondragover | 元素被拖动到指定区域的时候触发的事件 |
ondrop | 当放置被拖动元素时触发的事件 |
1.4、event 事件因子
取到事件因子有两种方式,除了键盘事件,还可以在任何事件的触发函数中使用 window.event 取到事件因子对象。
代码示例如下:
<button id="btn">单击我查看事件因子</button>
<script>
var btn = document.getElementById("btn");
btn.onclick = function(e) {
var evn = e || window.event;
console.log(evn);
}
</script>
如上述代码所示,给按钮添加的是 onclick 鼠标事件,但依然可以在鼠标事件的函数中查看事件因子。
属性名 | 说明 |
---|---|
keyCode | 检测键盘事件相对应的 Unicode 字符码 |
srcElement | 返回触发事件的元素 |
type | 返回当前 event 对象表示的事件名称 |
button | 检查按下的鼠标键 |
x, y | 返回鼠标相对于 css 属性中有 position 属性的上级元素的 x 和 y 坐标 |
clientX, clientY | 返回鼠标在浏览器窗口区域中的 x 和 y 坐标 |
screenX,screenY | 返回鼠标相对于用户屏幕中的 x 和 y 坐标 |
altKey | 检查 Alt 键的状态。当 Alt 键按下时,值为 True;否则为 False |
ctrlKey | 检查 Ctrl 键的状态。当 Crtl 键按下时,值为 True;否则为 False |
shiftKey | 检查 Shift 键的状态。当 Shift 键按下时,值为 True;否则为 False |
toElement | 检测 onmouseover 和 onmouseout 事件发生时,鼠标所进入的元素 |
fromElement | 检测 onmouseover 和 onmouseout 事件发生时,鼠标所离开的元素 |
注意:检测鼠标按键的 button 属性仅用于 onmousedown、onmouseup 和 onmousemove 事件。对于其他事件,不管鼠标状态如何,都返回 0 (比如 onclick)。它有 8 个属性值,分别是 0 没按键、1 按左键、2 按右键、3 按左右键、 4 按中间键、5 按左键和中间键、6 按右键和中间键、7 按所有的键。
2、JavaScript 的事件模型
在 JavaScript 中,事件的绑定方式被称为“事件模型”。
2.1、DOM0 事件模型
DOM0 事件模型是最早诞生的事件模型,也是最常用的事件绑定方式。DOM0 模型有两种绑定事件的方式,分别是内联模型和脚本模型。
1.内联模型
内联模型又称为“行内绑定”,其绑定事件的方式是直接将函数名作为 HTML 标签某个事件的属性值。
// 基本语法如下
<button onclick="func()">按钮</button>
缺点:违反 W3C 关于 HTML 与 JavaScript 分离的基本原则。
2.脚本模型
脚本模型又称为“动态绑定”,其绑定的方式是通过在 JavaScript 中选中一个节点,并给节点的 onclick 事件添加监听函数。
// 基本语法如下
window.onload = function(){}
document.getElementById("div").onclick = function(){}
优点:实现了 HTML 与 JavaScript 分离,符合 W3C 的基本原则。
缺点:
1)同一节点只能绑定一个同类型事件,如果绑定多次,则只有最后一次生效。
2)一旦绑定事件,无法取消事件绑定。
2.2、DOM2 事件模型
DOM0 绑定事件的两种方式都有其局限性。为了解决 DOM0 事件模型所存在的局限性,DOM2 事件模型应运而生。
1.添加事件绑定
DOM2 事件模型的绑定相对于 DOM0 要稍微复杂一些,并且针对浏览器版本的不同,有两种不同的绑定方式。
1)针对 IE8 之前的浏览器,使用 attachEvent() 进行事件绑定。
// 基本语法如下
var btn = document.getElementById("btn");
btn.attachEvent("onclick", function(){
// onclick 触发时执行的回调函数
});
其中,attachEvent 接收两个参数。
- 第一个参数是触发的事件类型,主要事件名称需要用 “on” 开头。
- 第二个参数是触发事件时执行的回调函数。
2)除 IE8 之外的其他浏览器,统一使用 W3C 规范,使用 addEventListener() 进行事件绑定。
// 基本语法如下
var btn = document.getElementById("btn");
btn.addEventListener("click", function(){
// click 触发时执行的回调函数
}, true/false);
其中,addEventListener 接收 3 个参数。
- 第一个参数是触发的事件类型,主要事件名称不需要用 “on” 开头。
- 第二个参数是触发事件时执行的回调函数。
- 第三个参数是模型参数,表示事件冒泡或事件捕获,false(默认)表示事件冒泡,true 表示事件捕获。
3)为了能够兼容各种浏览器,可以采用兼容写法进行操作。
// 基本语法如下
var btn = document.getElementById("btn");
if (btn.attachEvent) {
// 判断浏览器如果支持 attachEvent,就用 attachEvent 进行绑定
btn.attachEvent;
} else {
// 如果浏览器不支持 attachEvent,就用 addEventListener 进行绑定
btn.addEventListener();
}
2.取消事件绑定
DOM2 和 DOM0 相比有一个非常重要的区别,就是使用 DOM2 绑定的事件可以取消事件绑定。如果要取消事件绑定,那么在绑定事件时,回调函数必须使用有名函数,而不能使用匿名函数。
// 基本语法如下
var btn = document.getElementById("btn");
// IE8 之前
btn.attachEvent("onclick", clickFunction);
// 其他浏览器
btn.addEventListener("click", clickFunction, true);
function clickFunction() {
// 单击事件的回调函数
}
为什么绑定的时候不能使用匿名函数作为回调函数呢?主要原因在于取消事件绑定的时候,语法要求必须传入需要取消的函数名。而匿名函数没有函数名,故无法取消。针对不同浏览器,取消事件绑定也有两种不同方式。
1)针对 IE8 之前使用 attachEvent() 绑定的事件,可以使用 detachEvent() 取消事件绑定。
// 基本语法如下
btn.detachEvent("onclick", 函数名);
2)针对其他浏览器使用 addEventListener() 绑定的事件,可以使用 removeEventListener() 取消事件绑定。
// 基本语法如下
btn.removeEventListener("click", 函数名);
3.DOM2 事件模型的优点
相对于 DOM0 事件模型,DOM2 的优点主要有以下几条:
1)实现了 HTML 与 JavaScript 的分离,符合 W3C 关于内容与行为分离的要求。
2)使用 DOM2 绑定的事件,可以取消事件绑定。
3)使用 DOM2 可以为同一节点添加多个同类型事件,多个事件可以同时生效,而不会被覆盖掉。
3、JavaScript 的事件流模型
所谓事件流,就是当一个节点触发事件时,事件会从当前节点流向其他节点,而根据事件流动的方向,事件流模型可以分为事件冒泡和事件捕获。基于事件冒泡,又诞生了一种新的事件绑定方式——事件委派。
3.1、事件冒泡
事件流指页面接收事件的顺序,当一个事件产生时,该事件传播的过程就是事件流。首先介绍事件流模型中的第一种类型——事件冒泡。
1.事件冒泡的概念
当某 DOM 元素触发某事件时,会从当前 DOM 元素开始,逐个触发其祖先元素的同类型事件,直到 DOM 根节点。
2.触发事件冒泡的情况
在人们接触到的事件绑定方式中,绝大部分都是事件冒泡。详细可以分为如下几种。
1)DOM0 事件模型绑定的事件均为事件冒泡。
2)IE8 之前使用 attachEvent() 添加的事件均为事件冒泡。
3)对于其他浏览器使用 addEventListener() 添加的事件,当第三个参数为 false 或省略时,为事件冒泡。
3.事件冒泡举例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style type="text/css">
#div1 {
width: 150px;
height: 150px;
background-color: blue;
}
#div2 {
width: 100px;
height: 100px;
background-color: red;
}
#div3 {
width: 50px;
height: 50px;
background-color: yellow;
}
</style>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script type="text/javascript">
document.getElementById("div1").onclick = function () {
console.log("触发 div1 单击事件");
}
document.getElementById("div2").onclick = function () {
console.log("触发 div2 单击事件");
}
document.getElementById("div3").onclick = function () {
console.log("触发 div3 单击事件");
}
</script>
</body>
</html>
上述三个 div 为相互嵌套的关系,使用 addEventListener() 添加 click 事件并设为事件捕获,当单击最内层的 div3 时,可以发现三个 div 的 onclick 事件都被触发,并且触发顺序是从祖先元素 div1 开始,依次触发到当前元素 div3。
3.2、事件捕获
事件捕获与事件冒泡类似,只是在事件流的方向上与事件冒泡恰恰相反。
1.事件捕获的概念
当某 DOM 元素触发某事件时,会从根节点开始,逐个触发其祖先元素的同类型事件,直到当前节点。
2.触发事件捕获的情况
相比与事件冒泡,事件捕获只有一种方式能够触发,即 IE8 之外的其他浏览器使用 addEventListener() 添加的事件,当第三个参数为 true 时,为事件捕获。
3.事件捕获举例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style type="text/css">
#div1 {
width: 150px;
height: 150px;
background-color: blue;
}
#div2 {
width: 100px;
height: 100px;
background-color: red;
}
#div3 {
width: 50px;
height: 50px;
background-color: yellow;
}
</style>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script type="text/javascript">
document.getElementById("div1").addEventListener("click", function () {
console.log("触发 div1 单击事件");
}, true);
document.getElementById("div2").addEventListener("click", function () {
console.log("触发 div2 单击事件");
}, true);
document.getElementById("div3").addEventListener("click", function () {
console.log("触发 div3 单击事件");
}, true);
</script>
</body>
</html>
上述三个 div 为相互嵌套的关系,使用 addEventListener() 添加 click 事件并设为事件捕获,当单击最内层的 div3 时,可以发现三个 div 的 onclick 事件都被触发,并且触发顺序是从祖先元素 div1 开始,依次触发到当前元素 div3。
3.3、事件委派
基于事件冒泡诞生了一种新的事件绑定方式——事件委派。
1.事件委派的概念
事件委派也叫事件委托,是将本该添加在节点自身的事件,选择添加到其父节点上,同时委派给当前元素来执行。
2.事件委派的原理
事件委派的原理就是事件冒泡。 由于给多个子元素添加事件,会沿着事件冒泡流触发其父元素的同类型事件,所以可以直接将事件添加在父元素上,并在事件函数中判断单击的是哪一个子元素,进而进行具体操作。
3.原生 JavaScript 实现事件委派
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
</head>
<body>
<ul id="ul">
<li>Java</li>
<li>C++</li>
<li>C#</li>
<li>JavaScript</li>
</ul>
<script type="text/javascript">
var ul = document.getElementById("ul");
ul.onclick = function (e) {
var evn = e || window.event;
console.log(evn);
var target = event.target || event.srcElement;
console.log(target);
if (target.nodeName.toLowerCase() === 'li') {
console.log(target.innerText);
}
}
</script>
</body>
</html>
从上述代码可以看到,将本该添加在 li 上面的单击事件,添加在 li 的父节点 ul 上,并在事件函数中取到事件因子,进而通过事件因子取到当前单击的元素。如果单击的元素是 li,则触发操作。
4.事件委派的作用
事件委派有很大用处,主要体现在如下两点:
(1)提高性能
将事件绑定在父节点上,只需要绑定一个事件就可以;而将事件依次添加给子节点,则需要绑定多个事件。因此,事件委派具有更优的性能。
(2)新添加的元素会具有同类型元素的事件
如果使用其他方式绑定事件,当页面新增同类的节点时,这些节点不会获得之前绑定的事件。但是,使用事件委派可以让新添加的元素获得之前绑定的事件。
3.4、阻止事件冒泡
事件委派的原理就是事件冒泡,但并不是所有的事件冒泡都是对开发有利的,实际开发中,大多情况并不想让子元素的事件影响到父元素。因此,阻止事件冒泡的传播也是一种重要方法。
对于阻止事件冒泡的写法,由于浏览器的兼容性问题,存在两种不同的写法。对于 IE8 以前的浏览器,可以将 e.cancelBubble 属性设为 true;对于其他浏览器,则可以调用 e.stopPropagation() 方法。当然也有兼容写法,基本语法如下:
function myParagraphEventHandler(e) {
e = e || window.event;
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
}
3.5、阻止默认事件
除了事件冒泡,还有一种情况存在妨碍开发的问题,那就是某些标签的默认事件。
取消默认事件也有两种常用方式。对于 IE8 之前的浏览器,可以将 e.returnValue 属性设置 false;对于 IE8 以外的浏览器,可以调用 e.preventDefault() 方法。同样,也有兼容写法,基本语法如下:
function eventHandler(e) {
e = e || window.event;
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}