3. JS Web-API-事件
3.1 面试题
1.编写一个通用的事件监听函数
2.描述事件冒泡的流程
3.无限下拉的图片列表,如何监听每个图片的点击
3.2 知识点
事件绑定
事件冒泡
事件代理
3.3 事件绑定
// 自定义通用的绑定函数
const clickBtn = document.getElementById('button1');
clickBtn.addEventListener('click', function () {
// e.preventDefault(); // 阻止默认行为
console.log('bindEventFn');
})
3.4 事件冒泡 & 事件捕获
两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
1.addEventListener
在了解事件冒泡和事件捕获之间,首先来了解下 addEventListener ,通过它我们可以实现事件的绑定。
语法:
element.addEventListener(event, function, useCapture)
第一个参数是需要绑定的事件(全部事件类型可查阅:https://www.runoob.com/jsref/dom-obj-event.html)
第二个参数是触发事件后要执行的函数。
第三个参数默认值是false,表示在事件冒泡的阶段调用事件处理函数,如果参数为true,则表示在事件捕获阶段调用处理函数。
2.事件冒泡
微软提出了名为事件冒泡(event bubbling)的事件流。
也就是说,事件会先从最内层的元素开始发生,一直向上传播,直到document对象。
( … -> body -> html -> document)
(受限于篇幅,在这只截取了关键代码)
// html
<div id="container1">
<div id="container2">
<div id="container3">
<button id="btn">点击</button>
</div>
</div>
</div>
// js
<script>
document.getElementById('container1').addEventListener('click', function (e) {
console.log('container1', e.target);
})
document.getElementById('container2').addEventListener('click', function (e) {
console.log('container2', e.target);
})
document.getElementById('container3').addEventListener('click', function (e) {
console.log('container3', e.target);
})
document.getElementById('btn').addEventListener('click', function (e) {
console.log('btn', e.target);
})
</script>
// css
<style>
#container1 {
position: relative;
width: 200px;
height: 200px;
background-color: blue;
}
#container2 {
position: relative;
width: 120px;
height: 120px;
background-color: yellow;
}
#container3 {
position: relative;
width: 70px;
height: 70px;
background-color: red;
}
</style>
点击按钮后输出顺序:btn 、container3 、container2 、container1
这是因为,事件冒泡的触发会先从最内层的元素开始发生,一直向上(外层 )传播
3.事件捕获
网景提出另一种事件流名为事件捕获(event capturing)。
与事件冒泡相反,事件会从最外层(document)开始发生,直到(触发事件的)最具体的元素。
( document -> html -> body -> …)
借用事件冒泡章节的代码,只需要给 addEventListener 添加第三个参数,即表示在事件捕获阶段调用处理函数。
// ...
document.getElementById('btn').addEventListener('click', function (e) {
console.log('btn', e.target);
}, true)
// ...
点击按钮后输出顺序:container1 、container2 、container3 、btn
这是因为,事件捕获的触发会先从最外层的元素(document )开始发生,一直向里(内层)传播。
3.5 事件代理
定义: 事件代理就是利用事件冒泡,只制定一个事件处理程序,就可以管理某一类型的所有事件
首先来看一个例子:
// 创建一个文档片段
const frag = document.createDocumentFragment();
const count = 10;
for (let i = 0; i < count; i += 1) {
const li = document.createElement('li');
li.innerHTML = `li - ${i} 标签`;
frag.appendChild(li);
}
document.body.appendChild(frag);
要求:点击某个 li 标签,弹出对应的序号,弹出标签文本
做法一:可以为每个li标签绑定一个事件,如这样
for (let i = 0; i < count; i += 1) {
const li = document.createElement('li');
li.innerHTML = `li - ${i} 标签`;
li.onclick = function () {
alert(`li - ${i} 标签`);
}
frag.appendChild(li);
}
问题来了,这里只有 10 个 li 标签,这样做是肯定没问题的,但是如果有一万个 li 标签,难道要为每个 li 标签绑定事件吗,这样子会十分消耗性能。
做法二:这时候可以使用事件代理,只需要在 ul 标签上绑定一次事件即可
<ul id="ulContainer"></ul>
<script>
// 创建一个文档片段
const frag = document.createDocumentFragment();
const count = 10;
for (let i = 0; i < count; i += 1) {
const li = document.createElement('li');
li.innerHTML = `li - ${i} 标签`;
frag.appendChild(li);
}
document.getElementById('ulContainer').appendChild(frag);
document.getElementById('ulContainer').addEventListener('click', function (e) {
console.log(e.target);
alert(e.target.innerHTML);
});
</script>
通过 e.target 就可以获取到对应的触发元素
关于 target 和 currentTarget 可以看 JavaScript 随记中关于二者区别的文章
JS target 和 currentTarget 区别
注意,不要滥用事件代理,比如仅有一个(极少数)标签,自然是应该绑定到对应标签即可