事件
概述
事件可以在页面中的操作与逻辑代码绑定关联关系,在用户和代码之间建立交互。
尝试给按钮的点击事件绑定一个函数 foo
:
<button onclick="foo()">点击事件</button>
<script>
function foo() {
alert("点击就弹框");
}
</script>
上述代码会给这个按钮注册一个点击事件,当用户点击按钮的时候,会执行全局上的 foo
函数。
其中,onclick
表示一个点击 事件,foo
被叫做这个点击事件的 事件处理器,事件与事件处理器之间存在绑定关系。
事件绑定与解绑
为一个事件绑定事件处理器有三种方式:
- 单个绑定
- 多个绑定
- 元素绑定
单个绑定
元素的所有事件都有对应的元素属性,通过给对应的事件属性赋值一个函数,即可绑定对应的事件处理器,比如:
var bt0 = document.getElementsByTagName("button")[0];
bt0.onclick = function () {
console.log("事件执行了");
};
// 解绑
// bt0.onclick = null;
如果需要解绑事件,直接让对应事件的值为 null
即可。
当一个事件没有绑定时,它的默认值是 null
,元素不支持事件为 undefined
。 你可以使用此规则判断浏览器是否支持某个事件,比如:
var foo = document.getElementById("foo");
if (foo.onclick !== undefined) {
console.log("浏览器支持 onclick 事件");
}
多个绑定
也可以为同一个元素的同一个事件绑定多个事件处理器,使用以下方法
EventTarget.addEventListener
:为元素的某个事件绑定一个事件处理器EventTarget.removeEventListener
:解绑一个事件attachEvent
:绑定事件,IE9 以下使用detachEvent
:解绑事件,IE9 以下使用
示例:
var bt0 = document.getElementsByTagName("button")[0];
function foo() {
console.log("事件触发了");
}
bt0.addEventListener("click", foo);
// IE 低版本按照以下方式绑定
// bt.attachEvent("onclick", foo);
// 解绑
// bt0.removeEventListener(foo);
// bt0.detachEvent(foo);
如果事件处理器是一个匿名函数表达式,并且这个函数表达式没有被保存,那么将无法解绑事件,因为无法设置解绑的函数。比如:
var bt0 = document.getElementsByTagName("button")[0];
// 函数声明
function foo() {
console.log("foo,这个事件可以解绑");
}
bt0.addEventListener("click", foo);
// 解绑
bt0.removeEventListener(foo);
// 保存函数表达式
var bar = function () {
console.log("bar,这个事件可以解绑");
};
bt0.addEventListener("click", bar);
// 解绑
bt0.removeEventListener(bar);
// 绑定一个具名函数表达式
bt0.addEventListener("click", function baz() {
console.log("baz,这个事件可以在处理器中解绑,单击一次后解绑");
// 解绑
bt0.removeEventListener(baz);
});
// 绑定一个匿名函数表达式,无法解绑
bt0.addEventListener("click", function () {
console.log("匿名,这个事件无法解绑");
});
因为低版本 IE 没有 addEventListener
,你可以是自行编写兼容代码,如:
元素绑定
可以配合 HTML 代码绑定事件:
<button onclick="foo()">click</button>
<script>
function foo() {
console.log("事件绑定");
}
</script>
上述绑定事件的方式不被推荐,因为这样 JavaScript 的代码干扰了 HTML 结构。
事件对象 & 事件源
当事件触发的时候,系统会传给事件处理器一个参数,这个参数称作事件对象,里面包含了事件执行时的详细信息。 比如:
var bt0 = document.getElementsByTagName("button")[0];
bt0.addEventListener("click", function (e) {
console.log(e);
});
事件对象中有执行事件时的各种信息,不同的事件得到的事件对象有些许不同,稍后介绍各类事件时会逐一介绍对应的事件对象。
事件执行时,并不是所有情况下都会传入事件对象,比如:
<button onclick="test()">bt</button>
<script>
function test(e) {
console.log(e); // undefined
}
</script>
上面的情况可以通过主动传入事件对象来解决,比如:
<button onclick="test(event)">bt</button>
<script>
function test(e) {
console.log(e);
}
</script>
IE 低版本兼容性极差,可以使用 window.event
来获取,这是不标准的写法,因为 window.event
是动态的,并不安全:
var bt0 = document.getElementsByTagName("button")[0];
bt0.onclick = function (e) {
var event = e || window.event;
console.log(event);
};
事件对象上的 target
属性指向触发该事件的元素,这个元素被称作 事件源。低版本的 IE 中,事件源需要由事件对象上的 srcElement
属性获取。
<button onclick="test(event)">bt</button>
<script>
function test(e) {
var event = e || window.event;
var targetEl = event.target || event.srcElement;
targetEl.style.background = "red";
}
</script>
标准的事件源是通过 target
获取,srcElement
是兼容 IE 的写法。
事件流与冒泡捕获
事件触发时,事件会被分发到触发事件元素的父级及祖先上,观察下列代码:
<div>
<input type="text" />
</div>
<script>
var div = document.getElementsByTagName("div")[0];
div.addEventListener("input", function (e) {
console.log(e.target.value);
});
</script>
我们在 div
元素上绑定了一个表单输入事件,input
元素的输入事件,传递给了父级,父级 div
元素监听到了子级传递过来的事件,我们把这种事件传播的行为,称作 事件流。
事件流是双向的,从父到子传递的过程被称作 事件捕获、从子到父传递的过程被称作 事件冒泡。每次事件触发时,会 先进行事件捕获,再进行事件冒泡。
在 w3c 中,有对事件流传递过程的可视化模型。
通过指定 addEventListener
的第三个参数,可以指定事件在什么阶段触发。第三个参数是一个布尔值,true
为捕获阶段触发,false
为冒泡阶段触发。(IE9 以下事件流只有冒泡阶段)
示例(查看控制台):
事件流的传播可以阻止,在事件对象上提供了停止事件传播的方法:
Event.stopPropagation
:标准,停止事件传播。cancelBubble
:,停止事件冒泡,IE9 以下的事件流没有捕获阶段,只有冒泡阶段,所以attachEvent
绑定事件就没有第三个参数。
通过以下代码,可以在上面的例子中阻止事件传播:
document.getElementById("foo2").addEventListener(
"click",
function (e) {
e.stopPropagation();
},
true
);
如果你想让你的页面能顺畅运行在 IE9 以下,你可以在元素的原型上添加一个自定义绑定事件的方法:
function bindEvent(el, name, handle, useCatch) {
if (el.addEventListener) {
el.addEventListener(name, handle, useCatch);
} else {
el.attachEvent("on" + name, handle);
}
}
var foo = document.getElementById("foo");
bindEvent(foo, "click", function () {
console.log("事件绑定");
});
默认事件
很多事件有默认行为,比如超链接元素的 click
事件或者表单提交的 submit
事件,它们都有可能会跳转页面。
观察以下代码:
<form action="https://www.baidu.com/s">
<a href="https://www.iqiyi.com/">iqiyi</a>
<input type="text" name="wd" />
<input type="submit" value="search" />
</form>
上述代码中,无论点击 iqiyi
按钮还是点击 search
按钮,都会发生页面跳转,这是超链接点击事件和表单提交事件的 默认行为。
默认行为是可以阻止的,使用事件对象的 preventDefault
方法:
document.getElementsByTagName("form")[0].addEventListener("submit", function (e) {
e.preventDefault();
});
document.getElementsByTagName("a")[0].addEventListener("click", function (e) {
e.preventDefault();
});
低版本 IE 中不支持该方法,在低版本 IE 中,可以使用 event.returnValue = false
值来阻止默认事件。
document.getElementsByTagName("form")[0].addEventListener("submit", function (e) {
e = e || window.event;
e.returnValue = false;
e.preventDefault && e.preventDefault();
});
各类事件
JS 中事件繁多:
- 鼠标事件(鼠标点击,滑动)
- 键盘事件(键盘输入)
- 表单事件(表单改变,焦点获取或丢失等)
- 其他
鼠标事件
事件名 | 描述 |
---|---|
onclick | 单击 |
ondbclick | 双击(如果绑定了单击事件,那么单击事件也会触发) |
onmousedown | 鼠标按下 |
onmouseup | 鼠标松开 |
onmousemove | 鼠标移动 |
onmouseenter | 鼠标移入到该元素内部 不会冒泡 |
onmouseleave | 鼠标移出到该元素内部 不会冒泡 |
onmouseover | 鼠标移入到该元素上(从内部子级移入也会触发) |
onmouseout | 鼠标移出到该元素上(移出到内部子级也会触发) |
需要注意的是, onclick
和 ondbclick
都只有鼠标左键才能触发。
鼠标事件的事件对象都是由 MouseEvent
创建的,以下是 MouseEvent
事件对象提供的常用的属性:
属性 | 描述 |
---|---|
MouseEvent.offsetX/Y | 相对目标节点的内容边界的偏移量(在 border 上可能为负值) |
MouseEvent.clientX/Y | 相对视区的偏移量 |
MouseEvent.screenX/Y | 相对屏幕的偏移量 |
MouseEvent.shiftKey | 是否按下辅助键 shift 键 |
MouseEvent.ctrlKey | 是否按下辅助键 ctrl 键 |
MouseEvent.altKey | 是否按下辅助键 alt 键 |
鼠标滚轮事件
事件 | 描述 |
---|---|
onwheel | 使用滚轮时触发,IE 不支持 |
onmousewheel | 类似 onwheel ,非标准事件,不建议使用,Firefox 不支持 |
鼠标滚轮事件由 WheelEvent
创建,它的原型是 MouseEvent
,所以也可以使用 MouseEvent
的属性,WheelEvent
常用属性如下:
属性 | 对应事件 | 描述 | 兼容 |
---|---|---|---|
wheelDelta | onmousewheel | 滚动的抽象值 | Firefox 无效 |
wheelDeltaX | onmousewheel | X 轴滚动的抽象值 | Firefox 无效 IE 无效 |
wheelDeltaY | onmousewheel | Y 轴滚动的抽象值 | Firefox 无效 IE 无效 |
deltaX | onwheel | X 轴滚动量 | IE 无效 |
deltaY | onwheel | Y 轴滚动量 | IE 无效 |
deltaZ | onwheel | Z 轴滚动量 | IE 无效 |
deltaMode | onwheel | 滚动量的单位,0 为像素,1 为行,2 为页 | IE 无效 |
键盘事件
事件名 | 描述 |
---|---|
onkeydown | 键盘按键按下 |
onkeypress | 键盘按键按下并输入字符后触发,此事件在新标准中已废弃 |
onkeyup | 键盘按键松开 |
元素获取焦点后按下键盘按键会触发键盘事件,默认情况下,当点击页面中时,body
元素是获取到焦点的。
观察以下代码:
function bindKeyEvent(el, eventName, mes) {
el.addEventListener(eventName, function (e) {
console.log(mes);
});
}
bindKeyEvent(document, "keydown", "down");
bindKeyEvent(document, "keypress", "press");
bindKeyEvent(document, "keyup", "up");
点击键盘按键,上述三个事件会依次执行,但若长按键盘按键, keydown
和 keypress
会交替执行,某些浏览器上,长按键盘按键 keyup
也会同前两个事件交替执行。
键盘事件对象由 KeyboardEvent
创建,KeyboardEvent
会提供一下常用属性:
属性 | 描述 |
---|---|
MouseEvent.shiftKey | 是否按下辅助键 shift 键 |
MouseEvent.ctrlKey | 是否按下辅助键 ctrl 键 |
MouseEvent.altKey | 是否按下辅助键 alt 键 |
code | 键码 |
key | 键值 |
有一些其他的键码或键值,比如 keyCode
、char
、charCode
、which
等,它们可能是非标准的或者在新标准中被废弃,不建议使用。
表单事件
form
元素
事件名 | 描述 | 补充 |
---|---|---|
onsubmit | 提交表单 | 当表单内,属性 type="submit" 的 input 元素或者 button 元素被点击的时触发 |
onreset | 重置表单 | 当表单内,属性 type="reset" 的 input 元素被点击时触发 |
input、select、textarea
元素
事件名 | 描述 |
---|---|
onchange | 焦点转移后有数据改变时触发 |
oninput | 数据改变时触发,低版本 IE 不兼容 |
其他事件
焦点
focus
不冒泡blur
不冒泡
滚动事件滚轮
scroll
不冒泡
窗口大小变化
resize
不冒泡
补充
主动触发事件
每个事件都可以主动触发,在元素属性上,有各类事件的触发方法,比如 onclick
事件使用 click
方法触发:
<button id="foo">click</button>
<script>
var foo = document.getElementById("foo");
foo.addEventListener("click", function () {
var pEl = document.createElement("p");
pEl.innerText = "content ";
document.body.appendChild(pEl);
});
// 就算不主动点击按钮,每过 1.5 秒也会触发 foo 元素的点击事件
setInterval(function () {
foo.click();
}, 1500);
</script>
所有事件都能主动触发,元素上有各个事件的执行函数。
javascript 协议
我们的代码可以以内联的方式写在 HTML 元素中,所有的事件都支持 javascript 协议。比如:
<button onclick="javascript:alert('what?')">click</button>
在 HTML 的事件属性中,javascript:
这个前缀可以省略,于是变成:
<button onclick="alert('what?')">click</button>
你甚至可以编写多行代码甚至复杂的语句:
<button
id="foo"
onclick="
for(var i = 0 ; i < 100;i++){
console.log(i);
}
"
>
click
</button>
现在再来看看 HTML 内联事件绑定的方式:
<button id="foo" onclick="clickHandle()">click</button>
<script>
function clickHandle() {
// ...
}
</script>
是不是一目了然了,它其实不是特殊语法,而是点击按钮的时候,执行了一个函数。
javascript 协议不止可以编写到事件上,还可以通过 href
属性放在 a
元素中,在点击链接的时候从而执行 JavaScript 代码,比如:
<a href="javascript: alert(1)">click</a>
如果最后一个语句的值不为 undefined
那么页面会展示链接里的内容:
<a href="javascript:'<strong>这里是链接里的内容</strong>'">click</a>
点击后就出大问题了,页面的没有跳转,但是展示了新的内容,并且不能通过后退键返回上级,只能刷新页面(IE 低版本刷新都没用,需要新建标签页)。
为了防止上述情况发生,在很久很久很久以前,让一个链接无法跳转可以编写以下代码:
<a href="javascript: void(0);">click</a>
配合 void
关键字,表达式的值永远是 undefined
,可以让链接不进行跳转并且不渲染其他内容,你可以在圆括号内编写任何代码而不影响页面。但是这种方式现在已经不再提倡,因为超链接的语义就是指定一个网络资源的地址,如果为了防止跳转而设置无用的地址,完全是本末倒置。 这里介绍的原因是很多老旧页面还存在这样的链接,相当于知识点补漏。
如果需要让一个链接无法跳转,应该使用 JS 禁止其默认事件。比如:
<a href="https://www.bilibili.com" data-prevent>bilibili</a>
<script>
document.body.addEventListener(
"click",
function (e) {
e = e || window.event;
var target = e.target || e.srcElement;
if (target.hasAttribute("data-prevent")) {
e.returnValue = false;
e.preventDefault && e.preventDefault();
}
},
false
);
</script>
上面代码中,设置自定义布尔属性 data-prevent
的元素都会被禁止默认事件,从而让 a
元素无法产生跳转。
备注:非常不提倡把 JavaScript 内联写到 HTML 中。