由点击页面其它地方隐藏div所想到的jQuery的delegate


Posted in Javascript onAugust 29, 2013

先从最简单的开始,假如页面有一个id为test的div,我们要实现点击页面其它地方隐藏该div:

<div id="test" style="margin:100px;background-color:#3e3;width:100px;height:100px;">        </div>

对于这个问题一般有两种思路,这两种思路都会利用事件冒泡这一原理,想要详细了解Javascript事件机制可以看看JavaScript与HTML交互——事件,这不是本文重点,所以这里只是简单介绍一下事件冒泡,

事件冒泡

IE的事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的元素

Netscape的事件捕获:不太具体的节点更早接收事件,而最具体的元素最后接收事件,和事件冒泡相反

DOM事件流:DOM2级事件规定事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段,首先发生的是事件捕获,为截取事件提供机会,然后是实际目标接收事件,最后是冒泡句阶段。

Opera、Firefox、Chrome、Safari都支持DOM事件流,IE不支持事件流,只支持事件冒泡

如有以下html,点击div区域,按照不同的模型事件元素的click事件触发顺序如下所示:

<!DOCTYPE html >
<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Test Page</title>
</head>
<body>
    <div>
        Click Here</div>
</body>
</html>

由点击页面其它地方隐藏div所想到的jQuery的delegate

在触发DOM上的某个事件的时候会产生一个事件对象event,这个对象包含着所有与事件有关的信息,包括产生事件的元素、事件类型等相关信息。所有浏览都支持event对象,但支持方式不同。事件对象有一个方法(W3C:stopPropagation)/属性(IE:cancelBulle=true)可以阻止事件继续冒泡或捕获。我们如果想在事件冒泡到某元素时阻止冒泡可以写一个这样的兼容浏览器方法:

function stopPropagation(e) {//把事件对象传入
            if (e.stopPropagation) //支持W3C标准
                e.stopPropagation();
            else //IE8及以下浏览器
                e.cancelBubble = true;
        }

因为所有的浏览器都支持事件冒泡,浏览器兼容性考虑,我们一般绑定事件的的时候都会利用事件冒泡而不是事件捕获。了解了这个之后我们可以看看下面两种思路了。

思路一
第一种思路分两步

第一步:对document的click事件绑定事件处理程序,使其隐藏该div

第二步:对div的click事件绑定事件处理程序,阻止事件冒泡,防止其冒泡到document,而调用document的onclick方法隐藏了该div。

<script type="text/javascript">
            function stopPropagation(e) {
                if (e.stopPropagation) 
                    e.stopPropagation();
                else 
                    e.cancelBubble = true;
            }
            $(document).bind('click',function(){
                $('#test').css('display','none');
            });
            $('#test').bind('click',function(e){
                stopPropagation(e);
            });
        </script>

这样当点击页面非div区域的时候,直接或层层冒泡会调用document的onclick方法,隐藏该div,而点击div或其子元素的时候,事件总会冒泡的div本身,这时候会阻止事件继续冒泡,不会调用doument的onclick方法致使div被隐藏,从而完成了我们的需求。

思路二

我们之前提到,在触发DOM上的某个事件的时候会产生一个事件对象event,这个对象包含着所有与事件有关的信息,包括产生事件的元素、事件类型等相关信息,思路一中div的click事件处理程序传入的参数就是这个event对象。访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。直接为DOM元素添加事件处理程序时,event对象作为window对象的一个属性存在。

event对象包含了一个重要属性:target(W3C)/srcElement(IE),这个属性标识了触发事件的原始元素,思路二就是要利用这个属性。我们可以直接对document的click事件绑定事件处理程序,在事件处理程序中判读事件源是否为id==test的div元素或其子元素,如果是则方法return不做操作,如果不是则隐藏该div。

<script type="text/javascript">
            $(document).bind('click',function(e){
                var e = e || window.event; //浏览器兼容性
                var elem = e.target || e.srcElement;
                while (elem) { //循环判断至跟节点,防止点击的是div子元素
                    if (elem.id && elem.id=='test') {
                        return;
                    }
                    elem = elem.parentNode;
                }
                $('#test').css('display','none'); //点击的不是div或其子元素
            });
        </script>

这样当点击页面任何地方的时候都会层层冒泡至document的click事件,事件处理程序会判断事件源是否为id==test的div或其子元素,如果是方法return,否则隐藏该div,也能够实现我们的需求。

注意点及优劣

这两种思路都依赖于事件冒泡,所以我们在处理其它相关元素的click事件的时候一定要注意这点,避免其他相关元素的click事件处理程序中包含阻止事件冒泡代码而影响了该功能。

这两种方式都很容易理解,貌似思路一更优秀一些,看起来它的处理更简单一些,不用去层层判断事件源,直接把click事件绑定在该div上。在这个例子中确实如此,但是有些复杂的页面就不尽然了,假如我们有一个页面,上面有数十个div都需要点击页面其它地方隐藏这类问题

<div class="dialogs">
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        <div class="dialog">
            <div id="1">1</div>
            <div id="2">2</div>
        </div>
        ...
    </div>

我们用思路一写出的代码可能是这样:

<script type="text/javascript">
            function stopPropagation(e) {
                if (e.stopPropagation) 
                    e.stopPropagation();
                else 
                    e.cancelBubble = true;
            }
            $(document).bind('click',function(){
                $('.dialog').css('display','none');
            });
            $('.dialog').bind('click',function(e){
                stopPropagation(e);
            });
        </script>

看起来简单依旧的样子,但是我们仔细想想就会发现问题,我们在每个dialog上都绑定了类似的方法,维护如此多的click事件处理程序对内存来说绝对是可开销,导致我们页面运行缓慢。而且如果我们可以动态使用ajax创建新dialog问题又来了,新创建的dialog不能实现隐藏功能!因为绑定函数已经执行完了,不会再为新的dialog绑定click事件处理程序,我们只能自己来做此事。也就是说思路一无法把处理程序附加到可能还未存在于DOM中的DOM元素之上。因为它是直接把处理程序绑定到各个元素上,它不能把处理程序绑定到还未存在于页面中的元素之上。

这时候就是思路二展示身手的时候了,我们看看思路二在这种时候代码的书写

<script type="text/javascript">
            $(document).bind('click',function(e){
                var e = e || window.event;
                var elem = e.target || e.srcElement;
                while (elem) {
                    if (elem.className && elem.className.indexOf('dialog')>-1) {
                        return;
                    }
                    elem = elem.parentNode;
                }
                $('#test').css('display','none'); 
            });
        </script>

改动也相当的小,我们来看看是不是能解决上边的两个问题了,首先无论多少个dialog我们只是绑定了一个click事件处理程序,对性能影响不大,添加一个新的dialog思路二的代码还好不好使呢,依旧好使,这样我们就能发现在复杂页面的情况下实际上思路二是一种更优秀的解决方案。

这些都明白了,我们就能说说本文的第二个主角jQuery的delegate方法了。

delegate
首先看看jQuery官方对delegate的语法及描述

.delegate( selector, eventType, handler(eventObject) )

Description: Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.

delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数。

使用 delegate() 方法的事件处理程序适用于当前或未来的元素(比如由脚本创建的新元素)。

$( "table" ).delegate( "td", "click", function() {
      $( this ).toggleClass( "chosen" );
    });

    通过上面语句我们就可以为所有table的td绑定click事件处理程序。

delegate方法设计意图在于把处理程序附加到单个元素上或是一小组元素之上,监听后代元素上的事件而不是循环遍历并把同一个函数逐个附加到DOM中的多个个元素上。把处理程序附加到一个(或是一小组)祖先元素上而不是直接把处理程序附加到页面中的所有元素上,从而带来性能上的优化。

jQuery版隐藏dialog

通过上面知识我们可以发现jQuery的delegate方法可以方便实现我们隐藏div的需求

<script type="text/javascript">
            $('.dialogs').delegate('.dialog','click',function(){
                $(this).css('display','none');
            });
        </script>

 使用jQuery我们发现比我们思路二在性能上又有了小幅提升,因为我们不需要冒泡至document处理了,只需要在dialog的父元素就可以处理完成了,可以不至于把很多类似功能都绑定到document上,需要注意的一点就是jQuery已经贴心的帮我们把this处理为事件源,处理起来更是如鱼得水了。

delegate与bind
通过上面我们说一堆我们可以在权衡使用bind还是delegate上有一定依据了,如果就单独绑定一个元素的事件处理程序,用bind还是很合适的,但是如果处理很多类似元素的事件处理程序的时候不妨考虑一下delegate,看看是否对提高性能有所帮助。

Javascript 相关文章推荐
window.location和document.location的区别分析
Dec 23 Javascript
js继承 Base类的源码解析
Dec 30 Javascript
围观tangram js库
Dec 28 Javascript
购物车选中得到价格实现示例
Jan 26 Javascript
jqueryMobile使用示例分享
Jan 12 Javascript
跨域资源共享 CORS 详解
Apr 26 Javascript
jQuery实现table中的tr上下移动并保持序号不变的实例代码
Jul 11 Javascript
JS生成和下载二维码的代码
Dec 07 Javascript
JS实现侧边栏鼠标经过弹出框+缓冲效果
Mar 29 Javascript
微信小程序 跳转方式总结
Apr 20 Javascript
jQuery实现左右滑动的toggle方法
Mar 03 jQuery
Vue编写可显示周和月模式的日历 Vue自定义日历内容的显示
Jun 26 Javascript
JavaScript自定义事件介绍
Aug 29 #Javascript
JavaScript包装对象使用介绍
Aug 29 #Javascript
JavaScript作用域链使用介绍
Aug 29 #Javascript
JavaScript 命名空间 使用介绍
Aug 29 #Javascript
JavaScript prototype 使用介绍
Aug 29 #Javascript
JavaScript创建对象的写法
Aug 29 #Javascript
jQuery实现用户注册的表单验证示例
Aug 28 #Javascript
You might like
dede3.1分页文字采集过滤规则详说(图文教程)续四
2007/04/03 PHP
php 3行代码的分页算法(求起始页和结束页)
2009/10/21 PHP
php addslashes 利用递归实现使用反斜线引用字符串
2013/08/05 PHP
PHP变量赋值、代入给JavaScript中的变量
2015/06/29 PHP
CodeIgniter辅助之第三方类库third_party用法分析
2016/01/20 PHP
CI框架实现优化文件上传及多文件上传的方法
2017/01/04 PHP
thinkPHP5.0框架配置格式、加载解析与读取方法
2017/03/17 PHP
PHP设计模式之简单工厂和工厂模式实例分析
2019/03/25 PHP
PHP实现爬虫爬取图片代码实例
2021/03/03 PHP
Jquery上传插件 uploadify v3.1使用说明
2012/06/18 Javascript
JavaScript对象创建及继承原理实例解剖
2013/02/28 Javascript
捕获键盘事件(且兼容各浏览器)
2013/07/03 Javascript
jQuery入门介绍之基础知识
2015/01/13 Javascript
深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP详解
2015/03/05 Javascript
详解vue 模拟后台数据(加载本地json文件)调试
2017/08/25 Javascript
在Vue.js中使用Mixins的方法
2017/09/12 Javascript
JS实现定时任务每隔N秒请求后台setInterval定时和ajax请求问题
2017/10/15 Javascript
javascript实现倒计时效果
2020/02/17 Javascript
Python实现将DOC文档转换为PDF的方法
2015/07/25 Python
Python脚本简单实现打开默认浏览器登录人人和打开QQ的方法
2016/04/12 Python
利用Python实现颜色色值转换的小工具
2016/10/27 Python
Python编程实现的简单Web服务器示例
2017/06/22 Python
Python3 Post登录并且保存cookie登录其他页面的方法
2018/12/28 Python
判断python对象是否可调用的三种方式及其区别详解
2019/01/31 Python
windows下安装Python虚拟环境virtualenvwrapper-win
2019/06/14 Python
利用python-pypcap抓取带VLAN标签的数据包方法
2019/07/23 Python
如何完美的建立一个python项目
2020/10/09 Python
Python爬虫新手入门之初学lxml库
2020/12/20 Python
介绍一下Ruby中的对象,属性和方法
2012/07/11 面试题
营销专业应届生求职信
2013/11/26 职场文书
办公室内勤岗位职责范本
2013/12/09 职场文书
我的理想演讲稿
2014/04/30 职场文书
小学感恩教育活动总结
2014/07/07 职场文书
四风自我剖析材料
2014/09/30 职场文书
2015年控辍保学工作总结
2015/05/18 职场文书
教你用python实现一个无界面的小型图书管理系统
2021/05/21 Python