先来了解一下事件冒泡和事件捕获:
事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。
事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。
事件委托依靠的就是事件冒泡和事件捕获的机制。
事件冒泡应用场景:
<style type="text/css"> #box1 { 300px; height: 300px; background: blueviolet; } #box2 { 200px; height: 200px; background: aquamarine; } #box3 { 100px; height: 100px; background: tomato; } div { overflow: hidden; margin: 50px auto; } </style> <body> <div id="box1"> <div id="box2"> <div id="box3"></div> </div> </div> <script> function sayBox3() { console.log('你点了最里面的box'); } function sayBox2() { console.log('你点了最中间的box'); } function sayBox1() { console.log('你点了最外面的box'); } // 事件监听,第三个参数是布尔值,默认false,false是事件冒泡,true是事件捕获 document.getElementById('box3').addEventListener('click', sayBox3, false); document.getElementById('box2').addEventListener('click', sayBox2, false); document.getElementById('box1').addEventListener('click', sayBox1, false); </script>
上面是三个具有父子关系的box,分别绑定了打印事件,现在点击最中间的红色box
可以看到,当为事件冒泡时,会从触发事件的元素一直往外冒泡直到document为止
事件捕获应用场景:
将false改成true,还是点击最中间的红色div
document.getElementById('box3').addEventListener('click', sayBox3, true); document.getElementById('box2').addEventListener('click', sayBox2, true); document.getElementById('box1').addEventListener('click', sayBox1, true);
可以看到,当为事件捕获时,从document根文档到当前元素触发事件
事件委托:又称之为事件代理
举个栗子来了解事件委托:
有三个同事预计会在周一收到快递,为了签收快递,有两种办法:1.三个人在公司门口等快递;2.委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收(可以给暂时不存在的节点也绑定上事件)。
现在有一个ul,ul里又有100个li,我想给这100个li都绑定一个点击事件,我们一般可以通过for循环来绑定,但是要是有1000个li呢? 为了提高效率和速度,所以我们这时可以采用事件委托,只给ul绑定一个事件,根据事件冒泡的规则,只要你点了ul里的每一个li,都会触发ul的绑定事件,我们在ul绑定事件的函数里通过一些判断,就可以给这100li都触发点击事件了。
具体代码:
<ul id="isUl"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>10</li> </ul> function clickLi() { alert('你点击了li'); } document.getElementById('isUl').addEventListener('click', function (event) { // 每一个函数内都有一个event事件对象,它有一个target属性,指向事件源 var src = event.target; // 我们判断如果target事件源的节点名字是li,那就执行这个函数 // target里面的属性是非常多的,id名、class名、节点名等等都可以取到 if (src.nodeName.toLowerCase() == 'li') { clickLi() } });
这样我们就通过给ul绑定一个点击事件,让所有的li都触发了函数。
那如果想给不同的li绑定不同的函数怎么办?
假设有3个li,我们先写3个不同的函数,再给3个li设置不同的id名,通过判断id名是不是就能给不同的li绑定不同的函数啦:
那如果想给不同的li绑定不同的函数怎么办?
假设有3个li,我们先写3个不同的函数,再给3个li设置不同的id名,通过判断id名是不是就能给不同的li绑定不同的函数啦:
<ul id="isUl"> <li id="li01">1</li> <li id="li02">2</li> <li id="li03">3</li> </ul> <script> function clickLi01() { alert('你点击了第1个li'); } function clickLi02() { alert('你点击了第2个li'); } function clickLi03() { alert('你点击了第3个li'); } document.getElementById('isUl').addEventListener('click', function(event) { var srcID = event.target.id; if(srcID == 'li01'){ clickLi01(); }else if(srcID == 'li02') { clickLi02(); }else if(srcID == 'li03') { clickLi03(); } }); </script>
这就是所谓的事件委托,通过监听一个父元素,来给不同的子元素绑定事件,减少监听次数,从而提升速度。
总结,事件委托的作用:
(1).提高性能:每一个函数都会占用内存空间,只需添加一个时间处理程序代理所有事件,所占用的内存空间更少;
(2).动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以具有和其它元素一样的事件。
(2).动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以具有和其它元素一样的事件。
那么,能不能阻止元素的事件冒泡呢,答案是可以的。
一开始那个例子,假如我们真的只想点击最里面的那个红色box,不想另外两个box的事件被触发,我们可以在给红色box绑定事件的函数里这么写
function sayBox3(event) { // 阻止冒泡 event.stopPropagation(); console.log('你点了最里面的box'); } document.getElementById('box3').addEventListener('click', sayBox3, false);
项目开发过程中阻止冒泡的用途:
看这个应用场景:
这是一个模态框,现在的需求是当我们点击红色的按钮需要跳转页面,然后点击白色的对话框不需要任何反应,点其它任何地方就关闭这个模态框。
这里就需要用到阻止冒泡了,红色的按钮是白色对话框的子元素,白色对话框又是这整个模态框的子元素,我们给模态框加上一个点击事件关闭,然后给红色的按钮加上一个点击事件跳转,这时就产生了一个问题,只要点击白色的对话框,由于冒泡机制,这个模态框也会关闭,实际上我们并不想点击白色的对话框有任何反应,这时我们就给这个白色的对话框绑定一个点击事件,函数里写上
这里就需要用到阻止冒泡了,红色的按钮是白色对话框的子元素,白色对话框又是这整个模态框的子元素,我们给模态框加上一个点击事件关闭,然后给红色的按钮加上一个点击事件跳转,这时就产生了一个问题,只要点击白色的对话框,由于冒泡机制,这个模态框也会关闭,实际上我们并不想点击白色的对话框有任何反应,这时我们就给这个白色的对话框绑定一个点击事件,函数里写上
event.stopPropagation();
,这样就OK了。IE兼容问题
老版本的IE存在兼容问题,根本不支持addEventListener()的添加事件和removeEventListener()的删除事件,它有自己的监听方法:
// 添加事件,事件流固定为冒泡 attachEvent(事件名,事件处理函数) // 删除事件 detachEvent(事件名,事件处理函数)
还有IE里的事件对象是window.event,事件源是srcElement,阻止冒泡写法也不一样:
function() { // IE里阻止冒泡 window.event.cancelBubble = true; // IE里获取事件源的id var srcID = window.event.srcElement.id; } function(event) { // 非IE里阻止冒泡 event.stopPropagation(); // 非IE里获取事件源的id var srcID = event.target.id; }
补充
关于js的浏览器兼容问题,一般用能力检测来解决,if(){}else{}
我们平时工作一般都是用jquery,IE这些特殊情况早就帮我们做好兼容啦。
jquery在1.7的版本之后,最流行的事件监听方法是$(元素).on(事件名,执行函数)
,它还有一种事件委托的写法$(委托给哪个元素).on(事件名,被委托的元素,执行函数)