打算封装一个弹窗组件,做的时候忘记了考虑事件冒泡的因素,结果被坑得不要不要的。为了解决自己的问题,去查阅了不少资料,把事件流相关的知识都给总结一下。
事件冒泡:一个简单,但是坑了我无数回的知识点!
JavaScript与HTML的交互通过事件来实现。而浏览器的事件流是一个非常重要的概念。不去讨论那些古老的浏览器有事件捕获与事件冒泡的争议,只需要知道在DOM2中规定的事件流包括了三个部分,事件捕获阶段、处于目标阶段、事件冒泡阶段。
事件捕获,是从document元素开始,越高级的父级元素越早接收到事件,因此这是一个从上而下的过程。
可能不少同学在初学js的时候遇到过这样一个坑。一个ul元素中初始的状态有4个li元素,我们可以循环给li元素添加click事件,执行我们想要的动作。这个例子的坑就在于,新添加的li元素不会有我们绑定的click事件。
<ul class="container"> <!-- 先循环给原有的4个li绑定click事件 --> <li class="item"></li> <li class="item"></li> <li class="item"></li> <li class="item"></li> <!-- 这是新添加的li元素,但是该元素却绑定click事件 --> <li class="item new"></li> </ul>
是的,就是这么坑,为了解决这个问题,我们就要利用事件捕获的原理。
$('ul.container').click(function(event) { var target = event.target; if (target.className == 'item') { // dosomething } })
在上面的解决方案中,我并不是直接给li绑定事件,而是给所有li的父级ul绑定事件。根据事件捕获的原理,事件会自上而下传递给li,我们只需要通过一些简单的条件判断来确定我们的目标元素即可,如上例中的判断li的className。
这个方式就是大名鼎鼎的事件委托。在jquery中,已经帮助我们实现了事件委托,我们只需要按照一定语法使用即可,而我们不用再自己去进行条件判断,比如我们要给所有的li.item元素绑定事件,写法如下
// on中的第二参数就是我们的目标元素的选择器 $('ul.container').on('click', 'li.item', function(event) { // dosomething })
事件委托是一个很重要而且在实际中会常常用到的知识点。
说完了事件捕获,然后来说说事件冒泡这个坑。所谓事件冒泡,就是让DOM树最底层的目标元素最先接收到事件,然后往上传递,这是一个自下而上的过程。
我们常常会遇到一种弹窗样式,弹窗出来时,内容在中间,然后会有一层半透明的遮罩将页面内容与弹窗区分开。弹窗内容会有一些按钮绑定点击事件,比如确认与取消。而在半透明遮罩上,可能也会绑定一个点击事件,当点击时,将弹窗隐藏。如果我由于经验不足,将该遮罩层设置成了弹窗按钮的父级,那么就会遇到事件冒泡带来的麻烦。
也就是说,在如下例子中 container是全屏遮罩,button是弹窗里面的点击按钮。他们都同时绑定了click事件,执行不同的动作。但是在实际执行的时候,当我点击了button,那么button和container的click事件都会执行,自下而上按顺序执行
<div className="container"> <div className="button">click</div> </div>
我为这个事情写了一个小例子,大家可以动手感受一下这个坑。点击空白会生成一个弹窗
http://yangbo5207.github.io/static/demo/event-flow/dev/
好吧解决问题的方法很简单,就是阻止冒泡事件。
$xxx.click(function(event) { event.stopPropagation(); })
理解了整个事件流,我们可以感受事件在DOM中的传递过程与方向,并且利用他来解决我们的问题和各种坑,虽然是一个简单的知识点,但是却非常容易被忽略,因此建议大家找机会将它掌握牢固。
总结完毕!