事件处理模型——事件冒泡、事件捕获
上一篇介绍了事件的绑定,我们这里先写一个三层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. 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; “复制代码”); “查看纯文本代码”); “返回代码高亮”)
我们点击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.灵活 当有新的子元素被加入的时候不需要重新绑定事件