1 事件流
事件流分为两步,一是捕获,二是冒泡
1.1 捕获概念
捕获就是从最高层一层一层往下找到最内部的节点
1.2 冒泡概念
捕获到最小节点后,一层一层往上返回,像是气泡从最底部往上冒一样,由于水深不同压强不同,同样多的气体越往上冒气泡越大,就像在html里越往上走“辈分”越大一样,所以比喻成冒泡
1.3 事件默认触发是在捕获阶段还是冒泡阶段?
默认是冒泡阶段
考虑如下层级
<div class="grandpa">
<div class="father">
<div class="son"></div>
</div>
</div>
为这三个div添加事件
const grandpa = document.querySelector(".grandpa");
const father = document.querySelector(".father");
const son = document.querySelector(".son");
grandpa.onclick = function() {
console.log('grandpa');
}
father.onclick = function() {
console.log('father');
}
son.onclick = function() {
console.log('son');
}
添加样式:
*{
margin: 0;
padding: 0;
}
.grandpa {
margin: 200px auto;
}
.grandpa {
width: 400px;
height: 400px;
background-color: blue;
}
.father {
width: 300px;
height: 300px;
background-color: skyblue;
}
.son {
width: 200px;
height: 200px;
background-color: pink;
}
考虑点击粉色区域时, 输出顺序是什么?点此跳转查看上述案例
son
father
grandpa
如果是捕获时触发事件,则顺序应当相反
1.4 阻止冒泡
son.onclick = function(event) {
console.log('son');
event.stopPropagation();
}
如上代码, 事件会默认向函数里传入一个event事件, 调用event.stopPropagation();
事件即可在当前位置阻止冒泡
这样点击son的显示区域时只会输出son
如果想要在点击son的区域时, 只将冒泡传递到father, 即触发father但是不触发grandpa的onclick事件, 可以如下操作:
const grandpa = document.querySelector(".grandpa");
const father = document.querySelector(".father");
const son = document.querySelector(".son");
grandpa.onclick = function() {
console.log('grandpa');
}
father.onclick = function(event) {
console.log('father');
event.stopPropagation();
}
son.onclick = function() {
console.log('son');
}
2 事件委托/事件代理
事件委托, 又称事件代理
顾名思义, 事件委托, 肯定是把事件委托到别处.
冒泡时触发事件, 那肯定是往上委托
也就是说, 将当前节点的事件交给该节点往上冒泡可达的先辈节点处理
这么说还是有点模糊, 准确来说, 就是son节点不绑定onclick事件, 而是等click事件冒泡到father身上, 在father身上绑定onclick事件, 在father的onclick事件中获取并处理被点击的son
事件冒泡时会将当前节点作为event事件的一个属性target传入, 也就是说相当于event.target = document.getElementById('son')
传入了father的onclick事件中
2.1 事件委托的好处
考虑下面例子
<ul class="father">
<li class="son">1</li>
<li class="son">2</li>
<li class="son">3</li>
<li class="son">4</li>
</ul>
当点击不同li的时候, 想要触发不同的事件, 例如输出1/2/3/4
传统实现逻辑如下:
const sons = document.querySelectorAll('.son');
for(const i = 0; i < sons.length; i++) {
sons[i].onclick = function() {
console.log(i);
}
}
如果页面内容非常多, 例如这里有1000个<li>
标签, 那么就需要添加1000次onclick函数, 十分麻烦
这种情况我们可以将事件委托给父元素.
这里需要用到 event.target
不妨试试对class为father的ul添加onclick事件, 输出event, 并点击li所在的区域, 查看event.target
输出的是什么
const father = document.querySelector('.father');
father.onclick = function(event) {
console.log(event.target);
}
我们点击第四个li:
可见, 在父元素ul上添加onclick事件, 点击子元素时, 冒泡时会将该子元素传递给过来
据此我们可以对子元素进行处理, 这样我们只在ul上绑定了事件, 就可以处理该ul下所有的li
点击尝试
const father = document.querySelector('.father');
father.onclick = function(event) {
console.log(event.target.innerText);
// 或者一些对点击的li的其他操作
}