事件其实前面也说过就是,其实就是在页面上的操作行为,比如滑动鼠标,点击等。给元素添加这些事件称之为注册事件或者绑定事件,前面注册事件的时候很多是直接对元素进行绑定,这些是传统的模式
,还有一种方法监听注册方法
。
注册方式
-
传统模式注册
前面使用的事件注册方式都是传统的注册方式,比如注册onclick等。
这种注册其实具有唯一性,那就是只能注册一种事件一次,如果这样说可能有点懵,所以演示一下。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试文档</title> <style> </style> </head> <body> <button id="bt">发布</button> <script> var bn1=document.getElementById("bt"); bn1.onclick=function () { alert("test1"); } bn1.onclick=function () { alert("test2"); } </script> </body> </html>
可以看出传统注册事件,只会执行其最一次注册的方式。
**补充:注册事件的唯一性是同一个元素注册同一个事件。**如果同一个事件注册多次,在传统模式中只有最后事件才能生效。
-
方法监听注册
这个是w3c标准推荐的方式,其通过addEventListener()这个方法进行实现,也有兼容问题,那就是IE9之前的版本不支持,可以用attachEvent代替,不过目前IE9的浏览器很少了,以及官方也不推荐使用,知道即可。
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
格式如下:
target.addEventListener(type, listener, options); target.addEventListener(type, listener, useCapture); target.addEventListener(type, listener, useCapture, wantsUntrusted); // Gecko/Mozilla only
-
type:
表示监听事件类型的字符串。其实一些前面使用的比如onclick,而这个参数是click,说一个不负责的话有些常用的在传统事件注册的时候,将前面的on去掉就是这事件监听中使用,其事件具体有的可以看一下官网:https://developer.mozilla.org/zh-CN/docs/Web/Events -
options 可选参数
一个指定有关 listener 属性的可选参数对象。可用的选项如下:
1:capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
2:once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
3:passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看 使用 passive 改善的滚屏性能 了解更多.
4:signal:AbortSignal,该 AbortSignal 的 abort() 方法被调用时,监听器会被移除。
5: mozSystemGroup: 只能在 XBL 或者是 Firefox’ chrome 使用,这是个 Boolean,表示 listener 被添加到 system group。 -
useCapture 可选
Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 事件流 及 JavaScript Event order 文档。 如果没有指定, useCapture 默认为 false 。 -
wantsUntrusted Non-Standard
如果为 true , 则事件处理程序会接收网页自定义的事件
来一个例子,证明可以注册多次
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试文档</title> <style> </style> </head> <body> <button id="bt">发布</button> <script> var bn1=document.getElementById("bt"); bn1.addEventListener("click",function(){ alert("test1"); }) function test2(){ alert("test2"); } // 也可以这样注册 bn1.addEventListener("click",test2) </script> </body> </html>
-
可以看出同一个元素同样的数据可以通过监听注册多次。
为了解决兼容问题一般会这样写:
function addEventListener(element,eventName,fn ) {
// 首先满足大多数的浏览器
if(element.addEventListener){
element.addEventListener(eventName,fn);
}else if(element.attachEvent ){
element.attachEvent('on'+eventName,fn);
}else{
//相当于类似element.οnclick=fn
element['on'+eventName]=fn;
}
}
删除事件
-
传统模式下删除事件,相对很简单。演示如下:
var bn1=document.getElementById("bt"); bn1.onclick=function () { alert("test1"); // 重新对事件进行定义赋值即可 bn1.onclick=null; }
-
方法监听注册
这个在删除事件的时候,就很难用重新赋值的方法了,毕竟可以注册多次。所以就需要用一个方法,格式如下:
target.removeEventListener(type, listener[, options]); target.removeEventListener(type, listener[, useCapture]);
演示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试文档</title> <style> </style> </head> <body> <button id="bt1">发布</button> <button id="bt2">发布</button> <script> var bn1=document.getElementById("bt1"); var bn2=document.getElementById("bt2"); bn1.addEventListener("click",function(){ alert("bt1_test1"); bn1.removeEventListener("click",function(){ alert("bt1_test1"); },false) },false) // 这样不会删除监听的,因为注册的时候是匿名函数,而这样删除也是匿名,两个匿名无法证明是同一个事件 function test2(){ alert("bt1_test2"); // 删除事件的通过函数名 其实后面的参数 false 和非false 有区别的 移出事件的时候需要注意,这个涉及事件 捕获和冒泡下来聊 bn2.removeEventListener("click",test2,false); } bn2.addEventListener("click",test2,false); </script> </body> </html>
为了兼容性如下写
function removeEventListener(element,eventName,fn ) {
// 首先满足大多数的浏览器
if(element.removeEventListener){
element.removeEventListener(eventName,fn);
}else if(element.detachEvent ){
element.detachEvent('on'+eventName,fn);
}else{
//相当于类似element.οnclick=null;
element['on'+eventName]=null;
}
}
DOM事件流
什么是事件流?
事件流是描述是从页面中接收事件的顺序。
在DOM中事件发生时会在元素节点之间按照待定的顺序传播,这个传播过程称之为DOM事件流。
可以先看图:
简单的DOM事件流分为3个阶段:
- 捕获阶段 (capture phrase):网景最早提出,由DOM最顶层节点开始,然后逐级向下传播到最具体(也就是触发的元素)元素接受的过程。
- 当前目标阶段(target phrase):触发事件的元素。
- 冒泡阶段(bubbling phrase):IE最早提出的,事件开始时由最具体的元素接收,然后逐级向上传播到DOM最顶层节点的过程。
注意:
- JS代码中只能执行捕获或者冒泡其中的一个阶段。
- onclike和attachEvent只能得到冒泡阶段。而且有些事件时没有冒泡的比如:onblur,onfocus,onmouseenter,onmouseeleave等。
target.addEventListener(type, listener[, useCapture])
第三个参数如果true,表示在事件捕获阶段调用事件处理程序。如果false(不写默认false)表示在事件冒泡阶段调用事件处理程序。
说实话如果单独看理论都会懵,所以具体通过演示理解。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div id="father1">
<div id="son1"></div>
</div>
<hr>
<div id="father2">
<div id="son2"></div>
</div>
<script>
var f1=document.getElementById("father1");
var s1=document.getElementById("son1");
s1.addEventListener('click',function () {
alert("son1")
},false)
// 如果不再父标签上添加一个同样的事件,那么所谓的事件流也就很难体现了
f1.addEventListener('click',function () {
alert("father1")
},false)
var f2=document.getElementById("father2");
var s2=document.getElementById("son2");
s2.addEventListener('click',function () {
alert("son2")
},true)
// 如果不再父标签上添加一个同样的事件,那么所谓的事件流也就很难体现了
f2.addEventListener('click',function () {
alert("father2")
},true)
</script>
</body>
</html>
如果这样看,事件流就是在父类和子类中绑定相同的事件,在触发的时候时先在父类响应还是子类中响应罢了。
其实这个会涉及到一个类似委派机制的东西,就是将有些事情自己不做,让其其它层级中处理,后面会演示。
其实在很多时候开发的时候更加关注的时冒泡阶段而不是事件捕获。
事件对象
只要有了事件就会创建事件对象,而且他是系统自动给我创建的,不需要参数传递。其包含了某个事件的相关数据。
这句话如果简单的说可能有点懵,先看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div id="father1">
<div id="son1"></div>
</div>
<hr>
<div id="father2">
<div id="son2"></div>
</div>
<script>
var s1=document.getElementById("son1");
var s2=document.getElementById("son2");
s1.addEventListener('click',function () {
console.log("----son1----");
console.log(arguments);
},false)
s2.onclick=function(){
console.log("----son2----");
console.log(arguments);
}
</script>
</body>
</html>
可以看出无论是传统注册方式还是监听注册方式,调用的方法默认都会传递一个参数,这个参数其实就是事件对象。
所以一般使用事件对象的时候,就会在触发事件的方法中写一个参数。
//event 只是一个参数名 可以随便写
s1.addEventListener('click',function (event) {
console.log("----son1----");
console.log(event);
},false)
s2.οnclick=function(event){
console.log("----son2----");
console.log(event);
}
这个事件对象,包含的事件的所有信息,可以在控制台看一下:
比如上面是鼠标点击行为,以及鼠标点击的行为,以及鼠标点击的元素等信息。
但是也有兼容性,在ie低版本中无法实现,所以兼容写法如下
function(event){
event=event||widow.event;
console.log(event);
}
所以可以看出event对象代表事件的状态,比如键盘按键的状态,鼠标的位置,鼠标的状态等。
事件对象常用常见的属性或方法
事件属性方法 | 描述 |
---|---|
event.target | 返回触发事件的对象 标准 (有兼容性) |
event.currentTarget | 它总是指向事件绑定的元素,而 Event.target 则是事件触发的元素 |
event.srcElement | 返回触发事件的对象 非标准IE6-8 (有兼容性) |
event.type | 返回事件的类型比如click ,mouseover等 |
event.cancelBubble | 该属性阻止冒泡件 非标准IE6-8 (有兼容性) |
event.stopPropagation() | 该属性阻止冒泡件 标准 (有兼容性) |
event.returnValue | 该属性阻止默认事件(默认行为) 非标准 (有兼容性) 比如不让链接跳转等 |
event.preventDefault() | 该属性阻止默认事件(默认行为) 标准 (有兼容性) 比如不让链接跳转等 |
event.target 和event.currentTarget
看一下两者的区别,顺便也看一下与this是否有关。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div id="father1">
<div id="son1"></div>
</div>
<hr>
<div id="father2">
<div id="son2"></div>
</div>
<script>
var s1=document.getElementById("son1");
var f2=document.getElementById("father2");
s1.addEventListener('click',function (event) {
console.log("绑定在子盒子上点击子元素");
console.log("this== ",this);
console.log("event.target== ",event.target);
console.log("event.currentTarget== ",event.currentTarget);
console.log("event.currentTarget==this ",event.currentTarget==this);
},false)
f2.onclick=function(event){
console.log("绑定在父盒子上点击子元素");
console.log("this== ",this);
console.log("event.target== ",event.target);
console.log("event.currentTarget== ",event.currentTarget);
console.log("event.currentTarget==this ",event.currentTarget==this);
}
</script>
</body>
</html>
可以看出target 是触发事件的元素,而currentTarget是事件绑定的元素其与this是相同的。
event.type
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div id="father1">
<div id="son1"></div>
</div>
<script>
var s1=document.getElementById("son1");
var f2=document.getElementById("father2");
s1.addEventListener('click',function (event) {
console.log(event.type);
},false)
</script>
</body>
</html>
可以看出得到的是事件的操作类型。
阻止默认事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<p>Please click on the checkbox control.</p>
<form>
<label for="id-checkbox">Checkbox:</label>
<input type="checkbox" id="id-checkbox"/>
</form>
<div id="output-box"></div>
<script>
document.querySelector("#id-checkbox").addEventListener("click", function(event) {
document.getElementById("output-box").innerHTML += "Sorry! <code>preventDefault()</code> won't let you check this!<br>";
event.preventDefault();
}, false);
</script>
</body>
</html>
因为这个兼容性,所以如下写:
// 例子:
ele.οnclick=function(event){
//标准模式
event.preventDefault();
// 非标模式 低版本
event.returnValue;
// 还有一种只能在传统模式下使用 return false; 但是后面语句就无法在运行了。
}
阻止冒泡
冒泡行为有好,自然也有不好的地方,所以有些时候需要将其禁止。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
[id^="father"]{
width: 400px;
height: 400px;
background-color: #4a90e2;
border: 1px solid red;
}
[id^="son"]{
margin: 100px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div id="father1">
<div id="son1"></div>
</div>
<script>
var f1=document.getElementById("father1");
var s1=document.getElementById("son1");
s1.addEventListener('click',function (event) {
alert("son1");
if(event=event || event.stopPropagation){
// 标准下 取消冒泡
event.stopPropagation();
}else {
//非标准下取消冒泡
event.cancelBubble=true;
}
},false)
// 如果不再父标签上添加一个同样的事件,那么所谓的事件流也就很难体现了
f1.addEventListener('click',function () {
alert("father1");
},false)
</script>
</body>
</html>
委托机制
其实冒泡方式会有个类似委托的机制,简单的说就是父类下的子类有一些操作,可以交个父类操作。还是那句话用例子演示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
li{
list-style: none;
}
</style>
</head>
<body>
<ul id="test_father">
<li>艾尔登法环玩家:宫崎老贼你个老yb</li>
<li>战神玩家:卧槽,你惹奎爷干嘛。</li>
<li>塞尔达玩家:再说一遍,主人公叫林克,不叫塞尔达</li>
<li>神秘海域玩家:卧槽,真是臂力惊人,不亏为神秘臂力。</li>
</ul>
<script>
var ele_ul=document.getElementById("test_father");
ele_ul.addEventListener("click",function (event) {
event.target.style.backgroundColor="red"
alert(event.target.textContent);
},false)
</script>
</body>
</html>
鼠标事件对象
很多时候,无论是划过还是点击等都会有一个作品,前面聊过鼠标的一些触发行为,而现在搞一个x和y周坐标的例子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
<style>
img{
position: absolute;
display: none;
width: 100px;
transform:translate(-50%,-50%);
cursor: pointer;
}
</style>
</head>
<body>
<img id="fly" src="jpg/hudie.gif">
<script>
var ele_fly=document.getElementById("fly")
document.addEventListener("mousemove", function (event) {
console.log(event.clientY);
ele_fly.style.display="block";
ele_fly.style.top=event.clientY+"px";
ele_fly.style.left=event.clientX+"px";
},false)
</script>
</body>
</html>
键盘事件
前面聊了键盘事件,现在再聊一下,如下三个:
现在来弄一下其事件触发的时间前后,来一个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
</head>
<body>
<input id="test" type="text">
<script>
var ele_test=document.getElementById("test")
ele_test.addEventListener("keyup", function () {
console.log("keyup事件");
});
ele_test.addEventListener("keypress", function () {
console.log("keypress事件");
});
ele_test.addEventListener("keydown", function () {
console.log("keydown事件");
});
</script>
</body>
</html>
演示按非功能键:
如果点击功能键:
可以看出执行顺序是:keydown--》keypress--》keyup.
当然既然监控键盘,自然就可以监控你按了那个键,这个就需要事件的key。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试文档</title>
</head>
<body>
<input id="test" type="text">
<script>
var ele_test=document.getElementById("test")
ele_test.addEventListener("keyup", function (event) {
console.log("keyup事件:" ,event.key);
});
ele_test.addEventListener("keypress", function (event) {
console.log("keypress事件",event.key);
});
ele_test.addEventListener("keydown", function (event) {
console.log("keydown事件",event.key);
});
</script>
</body>
</html>
以前是keycode返回的ASCII对应的数字,而且keyup和keydown还不识别大小写 ,不过目前和key一样都返回内容了,而且也都识别大小写了,这样方便了很多。
这个监控其实京东有一个隐藏的监控那就是按键s会自动聚焦到搜索栏, 不再演示了。