js事件流机制
什么是事件流
在JavaScript中事件流是指一个事件沿特定数据结构传播的一个过程。整个事件流总共包含三个阶段(从dome2来说):1.事件捕获阶段、2.处于事件目标阶段、3.事件冒泡阶段。下面我们来看一个图,只要是谈到事件流都会看到的一个图:
从这个图里面我们可以清晰的看到整个事件流的执行过程,首先是从window开始,一步步的从上向下执行,此过程就是事件捕获阶段,当到达了事件的位置以后则处于事件目标阶段,之后会在向上冒泡,进入事件的冒泡阶段。
你可以吧整个dom看做是一盆水,水里放密度不同的物品,有的物品可以嵌在其他物品中,构成父子节点,有的相互独立,构成兄弟节点,当你的手从上去点你需要点的物品时,势必要先触碰水面,然后触碰到父节点,然后才是目标节点。触碰完成以后再把手拿出来,正好是一个相反的过程,这就与我们的事件流机制是一个道理。
事件绑定
下面来看一下下面这个示例代码:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
#p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; }
#c { width: 100px; height: 100px; border: 1px solid red; }
#d { width: 50px; height: 50px; border: 1px solid green; }
</style>
</head>
<body>
<div id="p">
parent
<div id="c">
<div id="d">
grant
</div>
child
</div>
</div>
<script type="text/javascript">
var p = document.getElementById('p'),
c = document.getElementById('c'),
d = document.getElementById('d');
p.addEventListener('click', function () {
alert('父节点捕获')
},true);
p.addEventListener('click', function () {
alert('父节点冒泡')
});
c.addEventListener('click', function () {
alert('子节点捕获')
},true);
c.addEventListener('click', function (e) {
alert('子节点冒泡')
});
d.addEventListener('click', function () {
alert('孙子节点捕获')
},true);
d.addEventListener('click', function (e) {
alert('孙子节点冒泡')
});
</script>
</body>
</html>
点击ID为d的元素,你会发现依次弹出的内容为:父节点捕获-->子节点捕获-->孙子节点捕获-->孙子节点冒泡-->子节点冒泡-->父节点冒泡。(在添加注册事件时,第三个参数为true则代表接受捕获事件。)
在上面的代码中我们稍作修改
d.addEventListener('click', function (e) {
alert('孙子节点捕获');
console.log(e);
},true);
打印结果如上图所示。下面我们看一下一些常用的属性含义
属性 | 描述 | DOM |
---|---|---|
bubbles | 返回布尔值,指示事件是否是起泡事件类型。 | 2 |
cancelable | 返回布尔值,指示事件是否可拥可取消的默认动作。 | 2 |
currentTarget | 返回其事件监听器触发该事件的元素。 | 2 |
eventPhase | 返回事件传播的当前阶段。调用事件处理程序的阶段:1 捕获;2 处于阶段;3 冒泡阶段;这个属性的变化需要在断点中查看,不然你看到的总是0 | 2 |
target | 返回触发此事件的元素(事件的目标节点)。 | 2 |
timeStamp | 返回事件生成的日期和时间。 | 2 |
type | 返回当前 Event 对象表示的事件的名称。 | 2 |
view | 与事件关联的抽象视图,发生事件的window对象 | 2 |
preventDefault | 取消事件默认行为,cancelable是true时可以使用 | 2 |
stopPropagation | 取消事件捕获/冒泡,bubbles为true才能使用 | 2 |
stopImmediatePropagation | 取消事件进一步冒泡,并且组织任何事件处理程序被调用 | 3 |
在事件程序中,this和currentTarget指代的是同一对象。
如果说在事件捕获阶段,将子节点移除,那么子节点的捕获和冒泡是否还会执行?那么在上面的代码中我们在做一些修改,来查看一下效果
c.addEventListener('click', function () {
alert('子节点捕获');
c.removeChild(d);
},true);
运行后我们发现,执行顺序没有变化,子节点的捕获和冒泡依然执行,这里就需要我们做一些优化了,不仅要移除子节点,还需要对节点的注册事件进行移除。
事件委托
不知道大家在平时的使用的时候有没有遇到过这样的一种情况,如果事件涉及到更新HTML节点或者添加HTML节点的时候,就会出现这样的一种情况,新添加的节点无法绑定事件,更新的节点也是无法绑定事件,表现的行为是无法触发事件
如下:
<button id="e">增加</button>
<ul id='myLink'>
<li class="child"> apple </li>
<li class="child"> banana </li>
<li class="child"> orange </li>
</ul>
e = document.getElementById('e');
e.addEventListener('click',function(){
var myLink = document.getElementById('myLink');
var child =document.createElement("li");
child.className="child";
child.innerText ="orange"
myLink.appendChild(child)
})
有个需求是点击li需要打印出其html内容,那么如果不用事件委托的话,应该是如下方法
function shuchu(){
this.init();
}
Contact.prototype.init = function(){
var child = document.querySelectorAll('.child');
for(var i=0;i<child.length;i++){
(function(j){
child[j].onclick = function(){
console.log(child[j].innerText);
}
})(i);
}
}
new shuchu();
我们会发现一个问题,就是每一个li都添加了一个监听事件,这样如果说li数量非常大的话就会产生性能问题,甚至造成页面卡顿崩溃,另一方面就是新增加的li并没有添加上点击事件。
如果用事件委托,则会很好的解决这两个问题
var link = document.getElementById("myLink");
link.addEventListener('click',function(e){
if(!!e.target&&e.target.className=='child'){
console.log(e.target.innerHTML);
}
})
事件委托采用注册一个事件则能监听子节点的所有事件,所应用的就是事件的冒泡。