1-JavaScript-ECMAScript核心-事件event
事件对象
当事件的响应函数出发时,浏览器都会传递一个对象作为回调函数的实参,这个实参就是事件对象,事件对象中存储了所有当前事件相关的信息,如:事件的触发者、触发时哪个按键被按下、触发时的鼠标坐标.......
来个示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.d1 {
width: 50%;
height: 100px;
border: 1px solid red;
}
.d2 {
margin-top: 20px;
width: 50%;
height: 50px;
border: 1px solid green;
line-height: 50px;
text-align: center;
}
</style>
</head>
<body>
<h1>在红框内移动鼠标,绿框内会实时显示当前鼠标的坐标</h1>
<div class="d1"></div>
<div class="d2"></div>
<script>
let d1 = document.getElementsByClassName('d1')[0];
let d2 = document.getElementsByClassName('d2')[0];
d1.onmousemove = function (event) {
d2.innerHTML = `x = ${event.clientX}; y = ${event.clientY}`;
}
</script>
</body>
</html>
事件冒泡
冒泡(bubble),指的是事件的向上传导,当元素上的某个事件被触发时,其祖先元素上的相同事件也会同时被触发。
冒泡的存在大部分情况下都是有利的,简化了我们的开发。
冒泡的发生只和元素的结构有关,和元素的位置无关。
要取消事件的冒泡要用到事件对象,取消冒泡有两种方式,这两种方式本质上是一样的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1 {
width: 100px;
height: 100px;
background-color: tomato;
}
#d2 {
background-color: yellow;
/* 冒泡的发生只和元素的结构有关,和元素的位置无关 */
/*left: 200px;*/
/*top: 200px;*/
/*position: absolute;*/
}
</style>
</head>
<body>
<div id="d1">
我是div标签
<div id="d2">我是子标签</div>
</div>
<script>
let body = document.body;
let d1 = document.getElementById('d1');
let d2 = document.getElementById('d2');
body.onclick = function (event) {
console.log('我是body标签的onclick事件');
};
d1.onclick = function (event) {
console.log('我是d1 div标签的onclick事件');
};
d2.onclick = function (event) {
// 方式一
// event.stopPropagation();
// 方式二
event.cancelBubble = true;
console.log('我是d2 div标签的onclick事件');
};
</script>
</body>
</html>
事件绑定
使用属性来绑定事件时,一个元素上同时只能为一个事件绑定一个响应函数,如果同时为一个事件设置了多个响应函数,则后面会覆盖掉前面的,如下示例所以:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="d1">点我</button>
<script>
let d1 = document.getElementById('d1');
d1.onclick = function (event) {
alert(1);
};
// 下面的事件会覆盖掉上面的事件
d1.onclick = function (event) {
alert(2);
};
</script>
</body>
</html>
怎么解决上面事件被覆盖的问题呢?
可以通过addEventListener
来处理,它有三个参数:
- 想要绑定的事件,注意,不要前缀
on
,即如,原来用onclick
绑定一个点击事件,在addEventListener
中,直接填写click
就行了。 - 回调函数。
- 是否在捕获阶段触发事件,默认为false,这个参数下个小节再说!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="d1">点我</button>
<script>
let d1 = document.getElementById('d1');
// 原来这么做
// d1.onclick = function (event) {
// alert(1);
// };
// // 移除绑定事件
// d1.onclick = null;
// 现在这么做
d1.addEventListener('click', function (event) {
alert(1);
});
d1.addEventListener('click', function (event) {
alert(2);
});
d1.addEventListener('click', function (event) {
alert(3);
});
// removeEventListener专门用于移除addEventListener添加的事件,对别的绑定方式的事件无效
// 但是移除时,需要指定事件本身和回调函数,也因此,如果需要移除事件,那么在添加事件时,就要把
// 回调函数单独写出来,就像下面的示例一样
function clickHandler(event) {
alert(4);
}
d1.addEventListener('click', clickHandler);
d1.removeEventListener('click', clickHandler);
</script>
</body>
</html>
事件传播
这个故事要从一个现象说起,你先运行下下面的示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1 {
width: 400px;
height: 400px;
background-color: tomato;
text-align: right;
}
#d2 {
width: 300px;
height: 300px;
background-color: palegoldenrod;
text-align: right;
}
#d3 {
width: 200px;
height: 200px;
background: pink;
text-align: right;
}
</style>
</head>
<body>
<div id="d1">
1
<div id="d2">
2
<div id="d3">3</div>
</div>
</div>
<script>
let d1 = document.getElementById('d1');
let d2 = document.getElementById('d2');
let d3 = document.getElementById('d3');
d1.addEventListener('click', function (event) {
alert(1);
});
d2.addEventListener('click', function (event) {
alert(2);
});
d3.addEventListener('click', function (event) {
alert(3);
});
</script>
</body>
</html>
你会发现,事件的传播就是冒泡。
那么关于事件的传播,微软和网景有着不同的理解:
- 微软认为,事件应该是由内向外传播,也就是先触发后代元素上的事件,再触发祖先元素上的事件,这就是事件冒泡。
- 网景认为,事件应该由外向内传播,也就是先触发祖先元素上的事件,再触发后代元素上的事件,这就是事件捕获。
这俩大佬出现了理念上的分歧,那个时候存在感不强的和事佬w3c就要劝架,经过一番如此这般之后,搞了个折中方案,将事件分为三个阶段:
-
事件捕获。
-
当你点击d3(上例的id为d3的div)时,按照w3c的规定,事件由外往内开始捕获,即老祖宗
window-->document-->....>body-->d1-->d2-->d3
,直至找到目标元素,谁是目标元素啊?谁触发的事件谁就是目标元素。 -
当然,不同的厂商对于老祖宗的定义不太一样,有的以window为准,有的以document......当然,主流还是window对象。
-
-
目标元素。事件捕获到目标元素,捕获停止。
-
事件冒泡:
- 当找到目标元素后,开始触发由内向外进行事件冒泡。
- 顺序:
d3-->d2-->d1-->body--....window
。
但从上例的现象来看,只有事件冒泡现象,而没有事件捕获现象。
注意,无论怎么着,事件只能触发一次!你总不能事件捕获触发一次,事件冒泡再触发一次吧!所以,捕获和冒泡只能二选一,默认情况下是事件冒泡,即由内而外触发事件。
那么你就想看事件捕获的现象怎么办?好办!还记得addEventListener
的第三个参数么?默认为false就表示事件触发以冒泡为准。所以,我们给第三个参数传递个true就是事件捕获了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1 {
width: 400px;
height: 400px;
background-color: tomato;
text-align: right;
}
#d2 {
width: 300px;
height: 300px;
background-color: palegoldenrod;
text-align: right;
}
#d3 {
width: 200px;
height: 200px;
background: pink;
text-align: right;
}
</style>
</head>
<body>
<div id="d1">
1
<div id="d2">
2
<div id="d3">3</div>
</div>
</div>
<script>
let d1 = document.getElementById('d1');
let d2 = document.getElementById('d2');
let d3 = document.getElementById('d3');
d1.addEventListener('click', function (event) {
alert(1);
}, true);
d2.addEventListener('click', function (event) {
alert(2);
}, true);
d3.addEventListener('click', function (event) {
alert(3);
}, true);
</script>
</body>
</html>
但事件捕获的现象怪怪的,所幸,用的不多,理解就好了。
事件委派
先来看一个现象:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1 {
width: 400px;
height: 400px;
background-color: tomato;
text-align: right;
}
#d2 {
width: 300px;
height: 300px;
background-color: palegoldenrod;
text-align: right;
}
#d3 {
width: 200px;
height: 200px;
background: pink;
text-align: right;
}
</style>
</head>
<body>
<input type="text" placeholder="输入网站名称" name="c" id="inp">
<button id="btn">添加网站到列表</button>
<ul id="ul">
<li><a href="javascript:;">搜狗</a></li>
<li><a href="javascript:;">网易</a></li>
<li><a href="javascript:;">百度</a></li>
</ul>
<script>
let btn = document.getElementById('btn');
let inp = document.getElementById('inp');
let ul = document.getElementById('ul');
let links = document.getElementsByTagName('a');
// 为每个a标签绑定一个事件,点击连接,打印其内的文本
for (let i = 0; i < links.length; i++) {
links[i].addEventListener('click', function (event) {
console.log(this.innerHTML);
})
}
btn.addEventListener('click', function (event) {
ul.insertAdjacentHTML('beforeend', `<li><a href="javascript:;">${inp.value}</a></li>`);
});
</script>
</body>
</html>
上例代码存在问题:
- 目前的事件绑定的代码写在了for循环中,for循环执行了几次就绑定了几个事件,同时产生了几个回调函数,但是回调函数的功能是一样的,这样做比较浪费内存。
- 当前的事件只会绑定给已有的元素,对于新增加的元素来说,想要触发同样的事件,需要手动绑定。
而我们希望绑定事件只发生一次,就可以应用到所有的元素上,包括新增的元素。
这就用到了事件委派,也叫事件委托:当多个元素绑定相同的响应函数时,可以统一将事件绑定给它们的共同的祖先节点。这样只需绑定一次即可让所有的元素都具有该事件,及时元素是新增的也会具有该事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1 {
width: 400px;
height: 400px;
background-color: tomato;
text-align: right;
}
#d2 {
width: 300px;
height: 300px;
background-color: palegoldenrod;
text-align: right;
}
#d3 {
width: 200px;
height: 200px;
background: pink;
text-align: right;
}
</style>
</head>
<body>
<input type="text" placeholder="输入网站名称" name="c" id="inp">
<button id="btn">添加网站到列表</button>
<ul id="ul">
<li><a href="javascript:;">搜狗</a></li>
<li><a href="javascript:;">网易</a></li>
<li><a href="javascript:;">百度</a></li>
</ul>
<script>
let btn = document.getElementById('btn');
let inp = document.getElementById('inp');
let ul = document.getElementById('ul');
let links = document.getElementsByTagName('a');
ul.addEventListener('click', function (event) {
// 这里不能用this,因为这里的this代指的是ul标签
// 在事件对象中的target属性表示触发事件的对象
// 如果点击的是a标签
if (event.target.tagName.toUpperCase() === 'A') {
console.log(event.target.innerHTML);
}
});
btn.addEventListener('click', function (event) {
console.log(111, inp.value);
if (inp.value === "") {
alert("网站名称为空,请重新输入");
} else {
ul.insertAdjacentHTML('beforeend', `<li><a href="javascript:;">${inp.value}</a></li>`);
}
});
</script>
</body>
</html>
常见事件
不同的事件有很多,所以,这里参考:https://developer.mozilla.org/zh-CN/docs/Web/Events
下面举一个键盘事件的例子,按键盘的上下左右键,来移动div标签。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
margin: 0;
padding: 0;
}
#d1 {
width: 100px;
height: 100px;
background-color: tomato;
text-align: center;
line-height: 100px;
position: absolute;
}
</style>
</head>
<body>
<div id="d1"></div>
<script>
let div = document.getElementById('d1');
// 绑定全局的键盘事件
document.addEventListener('keydown',function (event) {
div.innerText = event.key;
switch (event.key) {
case 'ArrowUp':
case 'Up':
div.style.top = div.offsetTop - 10 + 'px';
break;
case 'ArrowDown':
case 'Down':
div.style.top = div.offsetTop + 10 + 'px';
break;
case 'ArrowLeft':
case 'Left':
div.style.left = div.offsetLeft - 10 + 'px';
break;
case 'ArrowRight':
case 'Right':
div.style.left = div.offsetLeft + 10 + 'px';
break;
}
})
</script>
</body>
</html>
移入移出
移入移出共有两组四个方法:
-
onmouseenter/onmouseleave,移入/移出,用的较多。
-
onmouseover/onmouseout,移入/移出,跟上面有区别的就是,这俩货在经过子元素时,也会触发事件,不懂的话,看示例就能明白了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
.out {
position: absolute;
width: 200px;
height: 200px;
top: 20px;
left: 10px;
background: pink;
}
.inner {
position: absolute;
width: 100px;
height: 100px;
top: 50px;
background: skyblue;
}
</style>
</head>
<body>
<div class="out">
外部DIV
<div class="inner">内部div</div>
</div>
<script>
let out = document.getElementsByClassName('out')[0];
// 第一组,onmouseenter/onmouseleave
// out.onmouseenter = function () {
// console.log('onmouseenter移入');
// };
// out.onmouseleave = function () {
// console.log('onmouseleave移出');
// }
// 第二组,onmouseover/onmouseout
out.onmouseover = function () {
console.log('onmousemove移入');
};
out.onmouseout = function () {
console.log('onmouseout移出');
}
</script>
<br>
</body>
</html>