事件监听概念
例如最简单的鼠标单击事件
事件对象
事件传播
我们先思考一个问题
四个圆我们共用一个圆心,我们都知道叫同心圆
那么如果我们去点击这个圆的这个位置3
那么我们是摁住了哪个圆呢?
其实我们是摁住了所有圆,因为我们点击是最内层的圆,向外扩散所有圆都被选中
我们的网页事件监听也是类似的
但是还有一个问题
我们想知道的是,顺序到底是从内向外执行,还是从外向内执行
我们通过案例演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件传播</title>
<style>
#box1 {
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2 {
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3 {
width: 100px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox1.onclick = function(){
console.log('我是box1的onclick');
};
oBox2.onclick = function(){
console.log('我是box2的onclick');
};
oBox3.onclick = function(){
console.log('我是box3的onclick');
};
</script>
</body>
</html>
我们点击最内部的盒子也就是box3后
由此我们可以认为事件传播是从内到外的(经测试跟书写顺序无关)
但是轻易下结论往往是不成熟的
事件传播的3个阶段
js事件传播分为三个阶段:捕获阶段(由外向内),目标对象事件处理程序调用阶段,冒泡阶段(由内向外)。
捕获阶段
浏览器会从最外层元素一直向内层元素逐级查找,直到找到事件源为止,目的是为冒泡阶段的传播提供路径(event的原型里有一个path存放的就是捕获阶段收集的传播路径)
目标阶段
当事件到达目标节点时,事件就进入了目标阶段。事件在目标节点上被触发(执行事件对应的函数),然后会逆向回流,直到传播至最外层的文档节点。
冒泡阶段
事件在目标元素上触发后,并不在这个元素上终止,而是会按照捕获阶段收集的传播路径,从内到外触发其祖先元素的相关事件
onxxx写法只能监听冒泡阶段【0级监听】
也叫作DOM0级事件监听
这是一个历史遗留问题,DMO规则制定经历了几个阶段,而在最初始的DOM制定的版本和方案就只涉及了on系列事件,这些只能监听冒泡阶段的也称0级事件监听。
那么现在我们终于明白之前的例子就是从内到外的
监听捕获阶段【2级监听】
addEventListener() 方法
element.addEventListener(event, function, useCapture);
参数 | 描述 |
---|---|
event | 事件的类型 (如 “click” 或 “mousedown”). |
function | 事件触发后调用的函数。 |
useCapture(可选) [bool类型] |
用于描述事件是冒泡还是捕获 |
这次我们可以用该方法去监听捕获阶段了,我们用之前的案例进行演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件传播</title>
<style>
#box1{
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2{
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3{
width: 100px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.addEventListener('click', function() {
console.log('我是box2的冒泡阶段');
}, false);
oBox3.addEventListener('click', function() {
console.log('我是box3的捕获阶段');
}, true);
oBox3.addEventListener('click', function() {
console.log('我是box3的冒泡阶段');
}, false);
oBox3.onclick = function () {
console.log('我是box3的onclick');
};
oBox1.addEventListener('click', function() {
console.log('我是box1的冒泡阶段');
}, false);
oBox2.addEventListener('click', function() {
console.log('我是box2的捕获阶段');
}, true);
oBox1.addEventListener('click', function() {
console.log('我是box1的捕获阶段');
}, true);
oBox1.onclick = function () {
console.log('我是box1的onclick');
};
oBox2.onclick = function () {
console.log('我是box2的onclick');
};
</script>
</body>
</html>
我们来看结果:
阻止事件传播
——————————————————
批量添加事件监听产生的性能问题
我们通过此案例来讲解
<body>
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var lis = oList.getElementsByTagName('li');
// 批量添加监听
for(var i = 0; i<lis.length;i++)
{
lis[i].onclick=function() {
this.style.color = 'red';
}
}
</script>
</body>
但是这么写势必会消耗大量内存
对此类问题的解决有一个稍微过的去的方案
<body>
<button id="btn">按我添加新的li列表项</button>
<ul id="list"></ul>
<script>
var oBtn = document.getElementById('btn');
var oList = document.getElementById('list');
oBtn.onclick = function()
{
var oLi = document.createElement('li');
oLi.innerHTML = '我是列表项';
oList.appendChild(oLi);
};
</script>
</body>
现在是我们第一层的逻辑,接下来我们做第二层,也就是点击列表项就变红,由于元素本身不能自动获得事件监听,所以依旧得这样写
..........
oList.appendChild(oLi);
// 给新创建的这个节点添加监听
oLi.onclick = function()
{
this.sytle.color='red'
}
虽然我们是动态的,但还是就是添加一个就注册一个监听,还是每创建一个就消耗了一块额外的内存,所以性能问题我们只解决了一半,我们想要的性能需求就是监听不跟每个节点产生绑定关系,这就需要我们的事件委托了。
事件委托——解决此类性能问题
我们抽象一个例子出来:
比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学。
在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
事件对象
所以现在我们就可以重写,达到性能上的优化
<body>
<button id="btn">按我创建一个新列表项</button>
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var oBtn = document.getElementById('btn');
oList.onclick = function(e) {
e.target.style.color = 'red';
};
oBtn.onclick = function () {
var oLi= document.createElement('li');
oLi.innerText = '我是新来的';
oList.appendChild(oLi);
}
</script>
</body>
注意事项:
委托就是靠冒泡特性来找到目标元素的。
比如:onmouseleave 事件类似于onmouseout事件。 唯一的区别是 onmouseleave 事件不支持冒泡 。
onmouseenter 事件类似于onmouseover事件。 唯一的区别是 onmouseenter 事件不支持冒泡 。