神的尾巴

全栈工程师、独立开发者

0%

从一个未起效的stopPropagation看JS Event

未起效的stopPropagation

简单介绍下背景:我有一个通用登录弹窗模块,检测如果element设置了action-login-popup,点击后,会弹出通用的登录弹窗,但是如果没登录的话,要屏蔽element内子元素的事件。

按照原来的经验,在该元素响应事件中直接event.stopPropagation(); return false;搞起,结果发现并没有起效,what a fuck !!!

好吧,仔细看了下,event.stopPropagation()用法应该是没错,但是有一个问题,我绑定的事件哪个先执行呢?简单console.log下,果然是子元素的事件先响应了,怪不得没效果。

好吧,看来是时候研究下JS的事件的执行流程了。

这件事要追溯到N年前网景与微软IE的浏览器大战,当时浏览器技术发展很快,w3c组织也没完全成型,在JS事件捕获这件事上,两家公司产生了分歧:

  1. 微软的做法是,使用事件冒泡方式bubble,从目标元素冒泡到父元素
  2. 网景的做法是,使用事件捕获方式capture,从父元素一直捕获,直到目标元素

最终w3c定义规范,两种方式都用,先捕获到目标元素再冒泡到父元素

那如何使用bubble和capture模式呢?
首先需要说明的是,现在所有浏览器添加事件默认都是用的冒泡模式

常见的添加事件方法有三种

1. ele.onClick = func

这种基于DOM的方式,每次添加事件会替换原来事件,只能为冒泡模式

2. ele.attachEvent(‘onClick’, func)

这种仅为IE < 9的情况下支持,只支持冒泡模式,可以添加多个事件,按添加顺序执行

3. ele.addEventListener(‘click’, func, options)

这种方式为 IE >= 9、chrome、firefox大部分浏览器支持,支持冒泡模式和事件捕获模式,通过设置options修改模式,下面是个简单的例子:

HTML

1
2
3
4
5
6
7
8
<div id="parent">
<div id="ele1">
<h1>ele1</h1>
</div>
<div id="ele2">
<h1>ele2</h1>
</div>
</div>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
#parent {
width: 500px;
height: 300px;
background: #666;
}
#ele1,
#ele2 {
width: 200px;
height: 200px;
background: #fff;
float: left;
margin: 20px;
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var parent = document.getElementById('parent');
var ele1 = document.getElementById('ele1');
var ele2 = document.getElementById('ele2');

ele1.addEventListener('click', function() {
console.log('bubble 1')
});
ele1.addEventListener('click', function() {
console.log('capture 1')
}, true);
ele2.addEventListener('click', function() {
console.log('bubble 2')
});
ele2.addEventListener('click', function() {
console.log('capture 2')
}, true);
parent.addEventListener('click', function() {
console.log('bubble p')
});
parent.addEventListener('click', function() {
console.log('capture p')
}, true);

运行结果如下

执行事件的顺序还有一个关键因素是绑定的先后顺序

一些额外的小知识

IE < 9,是没有preventDefault和preventDefault的,需要设置event的returnValue和cancalBubble来,阻止默认事件和事件冒泡,polyfill如下:

1
2
3
4
5
6
7
8
9
10
if (!Event.prototype.preventDefault) {
Event.prototype.preventDefault=function() {
this.returnValue=false;
};
}
if (!Event.prototype.stopPropagation) {
Event.prototype.stopPropagation=function() {
this.cancelBubble=true;
};
}
觉得对你有帮助的话,请我喝杯咖啡吧~.