事件,即文档或浏览器中发生的一些特定交互的瞬间,咱们能够利用事件监听来预约事件,当事件发生的时候执行相应的处理程序。当事件发生在某个DOM节点上时,事件在DOM结构中进行一级一级的传递,这便造成了“流”,事件流便描述了从页面中接收事件的顺序。本文主要讨论事件流的三个阶段,及利用事件委托机制进行性能优化。html
关于事件流的理解,《JS高程三》中有个形象的比喻:浏览器
能够想象画在一张纸上的一组同心圆,若是你把手指放在圆心上,那么你的手指指向的其实不是一个圆,而是纸上全部的圆。...>换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。性能优化
————《JavaScript高级程序设计(第三版)》page 345app
DOM2级事件中规定事件流包含3个阶段:函数
捕获阶段性能
处于目标阶段优化
冒泡阶段spa
首先发生的是事件捕获阶段,此时事件尚未传递到目标节点对象上,因此咱们就有机会在这个阶段进行事件的截。而后是目标节点接收到事件,最后是事件冒泡阶段,能够在这个阶段对事件作出处理和响应。
咱们先定义一段简单的html结构:设计
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <div class="box"> <button type="button" name="button">click me</button> </div> </body> </html>
在事件捕获阶段中,先由不具体的节点(即上层节点)接收到事件,而后一级一级往下传递,直到最具体的目标节点
接收到事件。
在DOM2级事件规范中,要求事件从document
对象开始传递,可是诸如Chrome,Firefox等主流浏览器倒是从window
开始传递的。code
addEventListener
方法的第三个参数是一个布尔值(可选),指定事件处理程序是否在捕获或冒泡阶段执行。 当为true
时,则事件处理程序将在捕获阶段执行。误区:不管
addEventListener
的第三个参数是否为true
,三个阶段都会走一遍,这里的第三个参数,指的是处理程序将会在捕获或者冒泡阶段执行,比如是你想买菜,你能够在上班路上,或者下班路上完成买菜,但不管何时买菜,你都要把这两段路程走完。
document.querySelector('#btn').addEventListener('click', function () { console.log("btn was clicked"); },true); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); },true); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); },true); document.addEventListener('click', function () { console.log("document was clicked"); },true); window.addEventListener('click', function () { console.log("window was clicked"); },true);
点击click me
按钮后,控制台依次打印出执行结果:
window was clicked document was clicked body was clicked box was clikced btn was clicked
很明显能够看出,在捕获阶段,事件由window
对象开始,一级一级地向下传递,直到传递到最具体的button
对象上。
事件冒泡阶段与捕获阶段刚好相反,冒泡阶段是从最具体的目标对象开始,一层一层地向上传递,直到window
对象。addEventListener
方法默认就是从冒泡阶段
执行事件处理程序。
document.querySelector('#btn').addEventListener('click', function () { console.log("btn was clicked"); }); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); }); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); }); document.addEventListener('click', function () { console.log("document was clicked"); }); window.addEventListener('click', function () { console.log("window was clicked"); });
点击click me
按钮后,控制台依次打印出执行结果:
btn was clicked box was clikced body was clicked document was clicked window was clicked
上述过程示意图:
咱们可使用event.stopPropagation()
方法阻止事件冒泡过程,以防止事件冒泡而带来没必要要的错误和困扰。
示例:
document.querySelector('#btn').addEventListener('click', function (event) { console.log("btn was clicked"); event.stopPropagation(); }); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); }); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); }); document.addEventListener('click', function () { console.log("document was clicked"); }); window.addEventListener('click', function () { console.log("window was clicked"); });
点击click me
按钮后,控制台打印出执行结果显示,事件没有再向上冒泡传递给其余节点对象:
btn was clicked
每一个函数都是对象,都会占用内存,因此当咱们的页面中所包含的事件数量较多时,若是给每一个节点绑定一个事件,加上事件处理程序,就会形成性能不好。还有一个问题是,某个元素节点是后来经过JavaScript动态添加进页面中的,这时候咱们若是提早对它进行绑定,但此时该元素并不存在,因此会绑定事件会失败。解决上述两个问题的一个经常使用方案,就是使用事件委托
。
举例来讲:
document.querySelector('.box').addEventListener(function (event) { switch (event.target.id) { case "btn": console.log("btn was clicked"); break; case "btn-2": console.log("btn-2 was clicked"); break; default: console.log("box was clicked"); break; } }); $(".box").append("<button id='btn-2'>btn-2</button>");
简单说,事件委托就是把原本该本身接收的事件委托给本身的上级(父级,祖父级等等)的某个节点,让本身的“长辈们”帮忙盯着,一旦有事件触发,再由“长辈们”告诉本身:“喂,孙子,有人找你~~”。
恩,差很少就是这么个意思,可怜天下父母心。
水平有限,欢迎你们不吝指正。