Web前端的知识之旅哟—— 事件处理模型与事件委托

事件处理模型——事件冒泡、事件捕获

上一篇介绍了事件的绑定,我们这里先写一个三层div嵌套的结构并且给每一个div都加一个点击事件。


1.  
2. .wrapper {
3. width: 200px;
4. height: 200px;
5. background-color: red;
6. }
7. .box {
8. width: 100px;
9. height: 100px;
10. background-color: green;
11. }
12.  
13. .content {
14. width: 50px;
15. height: 50px;
16. background-color: black;
17. }
18.  
CSS; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

1


1.  
2. var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3. box = document.getElementsByClassName(‘box’)[0],
4. content = document.getElementsByClassName(‘content’)[0];
5. wrapper.onclick = function () {
6. console.log(‘wrapper’);
7. }
8. box.onclick = function () {
9. console.log(‘box’);
10. }
11. content.onclick = function () {
12. console.log(‘content’);
13. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们点击最外层的wrapper,控制台打印的wrapper。

点击box,却打印出来box和wrapper

点击content打印出来content、box、wrapper

这个现象就是我们所说的事件冒泡。

什么叫冒泡?

结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,子元素冒泡向父元素,结构上的自底向上。(这里的底是结构上的底,视觉上是自顶向下)

大部分事件都有事件冒泡现象,并且所有的浏览器都有事件冒泡

结构上的冒泡,和视觉的位置没有关系,我们看一下三个方块视觉上分开的例子:


1.  
2. .wrapper {
3. width: 200px;
4. height: 200px;
5. background-color: red;
6. position: absolute;
7. }
8. .box {
9. width: 100px;
10. height: 100px;
11. background-color: green;
12. position: absolute;
13. left: 200px;
14. top: 200px;
15. }
16.  
17. .content {
18. width: 50px;
19. height: 50px;
20. background-color: black;
21. position: absolute;
22. left: 100px;
23. top: 100px;
24. }
CSS; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

2

我们点击content部分之后依然会把box和wrapper都打印出来。

并不是所有的事件都有冒泡,focus、blur、change、submit、reset、select等方法就没有事件冒泡现象。

事件捕获:

结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素(事件源元素),结构上的自顶向下。

addEventListener最后一个参数就是是否开始事件捕获,当我们填true的时候,就代表开启了事件捕获。只要开启了事件捕获,就不会冒泡了,如果不捕获的话,就遵循事件冒泡。

因为addEventListener只有chrome有,因此事件捕获也只有chrome浏览器有

依然是上面的那个例子:


1.  
2. var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3. box = document.getElementsByClassName(‘box’)[0],
4. content = document.getElementsByClassName(‘content’)[0];
5. wrapper.addEventListener(‘click’, function (e) {
6. console.log(‘wrapper’);
7. }, true);
8. box.addEventListener(‘click’, function (e) {
9. console.log(‘box’);
10. }, true);
11. content.addEventListener(‘click’, function (e) {
12. console.log(‘content’);
13. }, true);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在点击content之后,顺序是wrapper、box、content。

当事件冒泡和事件捕获同时存在的时候,事件冒泡和事件捕获的触发顺序则为:先捕获,再冒泡


1.  
2. var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3. box = document.getElementsByClassName(‘box’)[0],
4. content = document.getElementsByClassName(‘content’)[0];
5. wrapper.onclick = function () {
6. console.log(‘wrappeBubbler’);
7. }
8. box.onclick = function () {
9. console.log(‘boxBubble’);
10. }
11. content.onclick = function () {
12. console.log(‘contentBubble’);
13. }
14. wrapper.addEventListener(‘click’, function (e) {
15. console.log(‘wrapper’);
16. }, true);
17. box.addEventListener(‘click’, function (e) {
18. console.log(‘box’);
19. }, true);
20. content.addEventListener(‘click’, function (e) {
21. console.log(‘content’);
22. }, true);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

结果是先捕获再冒泡。

但是当我们把捕获写到冒泡前面的时候,顺序好像发生了变化。

wrapper–>box–>contentBubble–>content–>boxBubble–>wrapperBubble

这里是因为点击content,并不属于冒泡,而是属于事件执行,我们先绑定的boxBubble,所以就先捕获,再事件执行,再冒泡,这与我们的结论没有冲突。

取消冒泡和阻止默认事件

有时候冒泡或者默认事件会对我们的功能造成影响,因此我们需要适时地取消冒泡和默认事件。

我们绑定事件的处理函数的时候,可以传递一个形参,代表我们的事件对象,一般是e或者event,系统会自动帮我们捕获事件源对象并且把事件源对象传入

取消冒的方法

1.w3c标准方法:event.stopPropagation()


1.  
2. var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3. box = document.getElementsByClassName(‘box’)[0],
4. content = document.getElementsByClassName(‘content’)[0];
5. content.onclick = function (e) {
6. console.log(‘content’);
7. e.stopPropagation();
8. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们点击content之后就没有冒泡了,但是点击box还是有冒泡的,因为我们没有取消box的冒泡。

IE9以及以下的版本不支持这个方法

2.event.cancelBubble = true

这个属性是IE的,不过一些高版本的浏览器也有这个属性,只要让这个属性的值等于true,同样也可以取消事件冒泡。

封装一个兼容性的取消事件冒泡的方法:


1.  
2.  
3. function stopBubble(event) {
4. if(event.stopPropagation) {
5. event.stopPropagation();
6. }else {
7. event.cancelBubble = true;
8. }
9. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

默认事件

当我们在浏览器中点击右键,会弹出一个菜单,这就是一个默认事件contextmenu。还有a标签,即使我们不写跳转的页面,也会自动刷新页面,这也是一个默认事件。

移动端的默认事件更多。

默认事件有好的也有不好的,这就需要我们把不需要的默认事件阻止掉。

阻止默认事件

1.return false

我们只要在处理函数最后写上 return false就可以阻止默认事件了。


1.  
2. document.oncontextmenu = function () {
3. console.log(‘menu’);
4. return false;
5. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们在页面上右键就不会出现菜单了。

不过要注意的是,这种写法只能用在句柄方式绑定的事件上

2.e.preventDefault()


1.  
2. documet.addEventListener(‘contextmenu’, function (e) {
3. console.log(‘menu’);
4. e.preventDefault();
5. },false);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这是w3c标准的阻止默认事件的方法,句柄也同样可以使用。

不过IE9以下不兼容

3.e.returnValue = false

这个是IE的方法,事件源对象上的属性returnValue代表是否有默认事件,直接返回false就可以阻止默认事件了。现在高版本的浏览器也有这个属性。


1.  
2. document.attachEvent(‘oncontextmenu’, function (e) {
3. e.returnValue = false;
4. });
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们也可以封装一个兼容性的阻止默认事件的方法了:


1.  
2. function cancelHandler(event) {
3. if(event.preventDefault) {
4. event.preventDefault();
5. }else{
6. event.returnValue = false;
7. }
8. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

小例子:阻止a标签不跳转


1.  
2. var a = document.links[0];
3. a.addEventListener(‘click’, funciton (e) {
4. e.cancelHandler(e);
5. },false);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样a标签就不会跳转了。

同时我们还可以用a标签的第四个用处,协议限定符来阻止默认事件。


1.  
2. <a href=“javascript: void(0); “>www.baidu.com</a>
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

不仅仅是0,只要填写一个代表false的值,就可以取消掉默认事件。

事件对象

在IE中,系统不会把事件对象传到方法中,因此我们的参数e或者event在IE中是不好用的,IE会把事件对象传递到window.event上,所以当我们使用事件对象的时候,就要写兼容性的写法:


1.  
2. var event = e || window.event;
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样就可以正常地获取到事件对象了。

事件委托

事件源对象

我们现在有一个ul,下面有十万个li,当我们给父级的ul添加第一个点击事件之后,由于事件冒泡的存在,不论我们点击哪一个li都会调用父级的点击事件处理函数,这个时候触发父级ul的点击函数的那个li就被称之为事件源对象。

event.target 火狐的获取事件源对象

event.srcElement IE的获取事件源对象

chrome两种都有

因此我们在获取事件源对象的时候也需要写兼容性写法, 配合刚才的事件对象的兼容性写法就是这个样子的:


1.  
2. oUl.addEventListener(‘click’, function (e) {
3. var event = e || window.event;
4. var tar = event.target || event.srcElement;
5. console.log(tar);
6. }, false);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们利用事件源对象和事件冒泡来处理的方式就叫做事件委托


1.  
2. oUl.addEventListener(‘click’, function (e) {
3. var event = e || window.event;
4. var tar = event.target || event.srcElement;
5. console.log(tar.innerHTML);
6. }, false);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

事件委托的优点

1.性能 不需要循环所有的子元素一个个绑定事件

2.灵活 当有新的子元素被加入的时候不需要重新绑定事件