天行健, 君子以自强不息
Sunny's Blog
Title

事件(1) 跨浏览器的事件处理程序总结(2016/06/28更新)

这块是前端比较重要的地方,需要深刻理解。我们说js与HTML之间的交互是通过事件实现的。页面接收事件是按照一个顺序的,也就是有相应的事件流,有意思的是,IE和其他浏览器的事件流正相反,IE是事件冒泡流,讲究从具体到一般;其他浏览器是事件捕获流,讲究从一般到具体。

上面这一段划掉的地方说的不对,不管是IE还是其他浏览器,事件触发的顺序都是从一般到具体,然后在从具体回溯到一般的,现代浏览器(其他+IE9及以上)利用addEventListener()监听事件,这个函数有三个参数,其中第三个参数默认是false,如果是true的话就是事件捕获的顺序了。为什么老版的IE只有事件冒泡呢,因为老版IE用的是attachEvent方法,这个方法只有两个参数,没得选。

对于事件处理程序来讲,目前有4种方式,其中HTML事件处理方式,就是直接在标签上绑个onclick之类的,因为缺点太多,尽量不用了。这里面多说一句,html的tag上绑事件这种方式对于事件属性名是不分大小写的,就是oncLICK,ONCLICK,你怎么写都可以的,但是js是区分大小写的。DOM0(不是标准,只是个时间点,IE4支持的DHTML)的方式比较简单,可以跨浏览器,但是不支持同时绑定多个事件处理程序,DOM2 IE8-不支持,IE也有一套自己的方式,我在下面都做了测试,例子的注释也比较详细。

对于事件对象,所有浏览器都有的属性包括: 1.判断event的类型;2.获取event的目标;3.阻止冒泡;4.阻止默认行为,当然IE和其他浏览器属性的获取方式也不一样的,下面同样进行了测试。

EventUtil是编写的可以跨浏览器的事件工具,可以直接用

                    //跨平台事件工具
                    var EventUtil = {


                        // 说明:只考虑DOM2方法和IE8-的方法即可包含现代全部浏览器
                        // 绑定事件
                        addEventFunction: function(element, type, funcName){
                            if ( element.addEventListener ) {
                                element.addEventListener( type, funcName, false );
                            }else{
                                element.attachEvent("on"+type, funcName );
                            };
                        },
                        //得到事件对象event
                        getEvent: function(event){
                            return event ? event : window.event;
                        },
                        //得到事件目标
                        getTarget: function(event){
                            return event.target || event.srcElement;
                        },
                        //取消事件的默认行为
                        preventDefault: function(event){
                            if ( event.preventDefault ) {
                                event.preventDefault();
                            }else{
                                event.returnValue = false;
                            };
                        },
                        //取消事件冒泡
                        stopBubble: function(event){
                            if (event.stopPropagation) {
                                event.stopPropagation();
                            }else{
                                event.cancelBubble = true;
                            };
                        },
                        // 解除事件
                        removeEventFunction: function(element, type, funcName){
                            if ( element.removeEventListener ) {
                                element.removeEventListener( type, funcName, false );
                            }else{
                                element.detachEvent("on"+type, funcName );
                            };
                        }
                    };

                    //绑定事件例子1
                    var div = document.getElementById("testDiv");
                    var testfunc = function(event){
                        //获取Event
                        var eventObj = EventUtil.getEvent(event);
                        //console.log(eventObj.type);
                        //获取事件目标
                        var target = EventUtil.getTarget(event);
                        //console.log(target);
                        alert("div");
                        //取消事件冒泡
                        EventUtil.stopBubble(event);

                    }
                    //绑定事件例子2
                    var a = document.getElementById("testa");
                    var testfunc2 = function(event){
                        //取消事件的默认行为
                        EventUtil.preventDefault(event);
                        alert("111");
                    }
                    //绑定事件例子2
                    var testfunc3 = function(event){
                        alert("body");
                    }


                    EventUtil.addEventFunction(div, "click", testfunc );
                    EventUtil.addEventFunction(a, "click", testfunc2 );
                    EventUtil.addEventFunction( document.body , "click", testfunc3 );

                    //////////////////////////////////////////////////////////////////////////
                    //事件处理程序
                    //DOM0 写法简单,跨浏览器的优势,不能添加多个事件处理程序,只执行最后一个
                    // var div = document.getElementById("testDiv");
                    // div.onclick = function(){
                    //     alert("111");
                    // }
                    // div.onclick = null;

                    //HTML的tag事件绑定也具有上面这种就近覆盖的方式


                    //DOM2 IE8不支持,可以添加多个事件处理程序
                    // var div = document.getElementById("testDiv");
                    // div.addEventListener("click",function(){
                    //     alert("222");
                    // }, false);
                    // div.addEventListener("click",function(){
                    //     alert("333");
                    // }, false);
                    //如果解除绑定的话,函数不能写成匿名函数。
                    //类似:div.removeEventListener("click", 函数名, false );才有效


                    //IE事件处理程序,和DOM0的区别:会在全局运行,this==window,
                    //和DOM2的区别:可以添加多个事件处理程序,但是顺序和DOM2的是相反的。
                    //IE8及以下都是5,4;IE9,10是4,5;IE11不支持了
                    // var div = document.getElementById("testDiv");
                    // div.attachEvent("onclick",function(){
                    //     alert("444");
                    // });
                    // div.attachEvent("onclick",function(){
                    //     alert("555");
                    // });
                    //如果解除绑定的话,函数不能写成匿名函数。
                    //类似:div.detachEvent("onclick",handler);才有效


                    //add 2014-10-09
                    //现代浏览器DOM事件总结
                    //gt IE8
                    //监听事件
                    var listenButton = document.getElementById("listen");
                    function callback(){
                        alert("listen success!");
                    }
                    //你可以吧callback写成匿名函数的,
                    //但是如果你要以后移除的话,就不能写成匿名函数
                    listenButton.addEventListener("click",callback);

                    //事件移除
                    listenButton.removeEventListener("click",callback);

                    //维护函数上下文
                    var weihuButton = document.getElementById("weihu");
                    var user = {
                        name: 'sunny',
                        say: function(){
                            alert("my name is "+this.name);
                        }
                    };
                    //优雅的写法:这里bind一个新的函数,保持user上下文关系
                    user.say = user.say.bind(user);
                    //或者用匿名函数,比较丑
                    weihuButton.addEventListener("click",user.say);

                    //事件在DOM中的流动过程包括IE9+
                    //DOM事件被触发的时候会经历三个阶段。
                    //1.从文档的根节点流向目标对象(捕获阶段)
                    //2.在目标对象上被触发(目标阶段)
                    //3.再回溯到文档的根节点(冒泡阶段)。
                    var outside = document.getElementById("outside");
                    var inside = document.getElementById("inside");
                    //开启捕获模式
                    outside.addEventListener("click",function(){
                        alert("outside-capture");
                        //用来阻止事件流中的后续事件
                        //event.stopPropagation();
                        //更猛烈的阻止同一个元素上绑定的其他事件
                        //event.stopImmediatePropagation();
                        //用来阻止浏览器默认事件,比如a
                        //event.preventDefault();
                    },true);
                    inside.addEventListener("click",function(){
                        alert("inside-capture");
                    },true);
                    //开启冒泡模式
                    inside.addEventListener("click",function(){
                        alert("inside-bubble");
                    },false);
                    outside.addEventListener("click",function(){
                        alert("outside-bubble");
                    },false);
                    
                

在最后补充两点:

1.在回掉函数中return false;等于stopPropagation()加上preventDefault();

2.event.target == $(this)用来判断是否是自身,这个方法我在obj见到同事calvin用过,那个场景用的不太合适,改成了event.target.tagName=="a",来解决了,但是这仍然是一个判断是否是自身的好方法。

                    //eg:
                    $(".test").click(function(event){
                        if ( event.target == $(this) ) {
                            ...
                        }
                    });
                

地势坤,君子以厚德载物