事件冒泡与事件捕获
引言:用户在浏览器操作的时候触发的一种行为就叫事件。
每个元素自身都有事件,只不过默认为null(没有事件,事件值就为undefined),当某个事件绑定一个函数之后,用户在操作浏览器的时候触发了这个事件,就会执行这个事件函数。
什么是事件冒泡和捕获呢?
来看一张图(简单明了~)
由上图我们可以得知
事件冒泡就是从目标元素自下而上一直到window(结束)这样一个过程
事件捕获就是从window自上而下一直到目标元素的这样一个过程
一般是先执行捕获,后执行冒泡
关于冒泡:
① DOM0,DOM2都有冒泡行为
② 目标元素和祖先元素绑定同一事件时候,目标元素触发的同时祖先元素也会执行这一事件
来,看个小栗子
DOM0 事件冒泡
window.onclick = function(){ alert('window'); } box1.onclick = function(){ alert('第一个'); }; box2.onclick = function(){ alert('第二个'); }; box3.onclick = function(){ alert('第三个'); }; //点击 box3分别会由下而上 弹第三个,第二个,第一个,window
DOM2 事件冒泡 div1.addEventListener('click',function(){ alert('red'); },false); div2.addEventListener('click',function(){ alert('green'); },false); btn.addEventListener('click',function(){ alert('按钮'); },false); //依次输出 按钮 -> green -> red
以上分别是DOM0和DOM2中的冒泡
冒泡的优点可以通过事件对象的target捕获到事件源是哪个元素,从而提高性能
但在开发中也会遇到冒泡带来的弊端(由于冒泡机制,子元素触发事件时候也会影响父元素和祖先元素)
比如以下效果:
let onOff = true; btn.onclick = function(ev){ if(onOff){ box.style.display = 'none'; }else{ box.style.display = 'block'; } onOff = !onOff; } document.onclick = function(){ box.style.display = 'none'; onOff = false; }
问题:点击按钮隐藏红色背景,再次点击按钮时候红色背景应该显示。但因为冒泡的特性,执行完按钮的onclick事件函数,还会执行document,所以再次点击按钮是没有反映的
解决方法:使用stopPropagation() 和 cancelBubble阻止冒泡的发生
let onOff = true; btn.onclick = function(ev){ if(onOff){ box.style.display = 'none'; }else{ box.style.display = 'block'; } onOff = !onOff; // ev.stopPropagation(); ev.cancelBubble = true; //阻止了btn的onclick的事件不往上面冒泡。 } document.onclick = function(){ box.style.display = 'none'; onOff = false; }
其中ev.stopPropagation是W3C标准写法,IE低版本不支持
ev.cancelBubble = true;这个方法虽然不是标准方法,但大多数浏览器都支持...
注意:开发中如果两个元素是嵌套关系就要小心时间冒泡了,名称尽量不要重复
关于捕获:
① DOM0是检测不到捕获的,只有DOM2/3才能捕获到
② addEventListener/removeEventListener中的布尔值默认都是false不捕获
div1.addEventListener('click',function(){ alert('red'); },true); div2.addEventListener('click',function(){ alert('green'); },true); btn.addEventListener('click',function(){ alert('按钮'); },true); //点击btn依次输出 red -> green -> 按钮
事件流(模型)
当一个事件触发时,一般会经历三个过程。即捕获阶段(window由上而下到目标元素)、目标阶段、冒泡阶段(目标元素由下而上到window),这么个过程称为事件流(事件模型)
其中目标阶段,他是按照事件绑定的先后顺序,而不是按照先捕获后冒泡这一规则来执行
下面是事件流的一些小测试
div1.addEventListener('click',function(){ console.log('red'); },true); div2.addEventListener('click',function(){ console.log('green'); },false); btn.addEventListener('click',function(){ console.log('按钮'); },false); function fn(){ console.log('tred');} btn.addEventListener('click',function(){ console.log('按钮2'); },false); div2.addEventListener('click',function(){ console.log('tgreen'); },true); div1.addEventListener('click',fn,true); div1.addEventListener('click',function(){ console.log('red'); },false); btn.addEventListener('click',function(){ console.log('t按钮'); },true); div2.addEventListener('click',function(){ console.log('green'); },false); btn.addEventListener('click',function(){ console.log('按钮'); },false);
//点击btn依次会输出下面结果 //red->tred->tgreen->按钮->按钮2->t按钮->按钮->green->green->red
PS:下面说下DOM0和DOM2 (木有DOM1...)
DOM0就是传统的事件,比如以on开头。DOM0检测不到事件捕获行为
DOM2有两个方法来处理事件:绑定 -> addEventListener()、解绑 -> removeEventListener()
绑定事件方法:
/* 第一个参数:不带on的事件名称 第二个参数:事件函数 第三个参数:布尔值,是否捕获。默认false不捕获 */ ele.addEventListener("不带on的事件名","事件函数","是否捕获")
解绑事件方法:
/* 第一个参数:不带on的事件名称 第二个参数:解绑的事件函数 第三个参数:布尔值,是否捕获。默认false不捕获 */ ele.removeEventListener("不带on的事件名","解绑事件函数","是否捕获")
除了上面的两种方法,还有IE下面的两个绑定、解绑方法
attachEvent('带on事件名',‘事件函数’)
detachEvent('带on事件名',‘事件函数’)
注意:这里的第一个参数是带on的事件名