• JS事件绑定及深入


    学习要点:

    1.传统事件绑定的问题
    2.W3C事件处理函数
    3.IE事件处理函数
    4.事件对象的其他补充

    事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。

    一 传统事件绑定的问题

    传统事件绑定中的内联模型不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。传统绑定如:

    window.onload=function(){
        var box=document.getElementById('box');
        box.onclick = function(){
            alert('Lee');
        };
    };

    问题一:一个事件处理函数触发两次事件

    如果一个页面有两个或者多个js,并且第一个js是第一个程序开发的,第二个js是第二个程序员开发的。第一个window.onload被覆盖了,如

    window.onload=function(){
        alert('Lee');
    };
    
    window.onload=function(){
        alert('Mr.lee');
    }  

    结果只是打印了 Mr.lee

    其实是有办法解决这个问题的,看下面这两种形式。
    a:

    alert(window.onload);//一开始没有注册window.onload,那么就是null
    
    window.onload=function(){
        alert('Lee');
    };
    
    alert(window.onload);//如果已经有window.onload,打印的是函数function
    
    window.onload=function(){
        alert('Mr.lee');
    } 

    b:

    alert(typeof window.onload);//一开始没有window.onolad,旧版火狐显示undefined,新版显示object,
    
    window.onload=function(){
        alert('Lee');
    };
    
    alert(typeof window.onload);//如果已经有window.onload,所有浏览器都会显示function
    
    window.onload=function(){
        alert('Mr.lee');
    }

    所以解决办法有了。

       window.onload=function(){
        alert('Lee');
    };
    
    if(typeof window.onload=='function'){
        var saved=null;//保存上一个事件对象
        saved=window.onload;
    }
    
    //saved 就是window.onload,saved()相当于window.onload(),但是window.onload()不能执行的
    //所以saved()相当于window.onload=function(){}
    
    window.onload=function(){
        if(saved){
            saved();//执行上一个事件 window.onload=function(){}
        }
        alert('Mr.lee'); //执行本事件
    }

    问题二:事件切换器
    切换一个id为box的div,让里面的背景red与blue直接切换,并且切换之前弹框一次,如:

    window.onload=function(){
        var box=document.getElementById('box');
        box.className="red";
        box.onclick=function(){
            alert('Lee'); //只执行了一次
            blue.call(this);//通过匿名函数执行某一函数,那么里面的this就是代表的window,所以可以通过call传递
        };
    }
    
    function blue(){
        this.className="blue";
        this.onclick=red;
        
    }
    
    function red(){
        this.className="red";
        this.onclick=blue;
    }

    上面的代码虽然实现了切换功能,但是弹框只执行了一次。

    //添加事件函数
    //obj相当于window
    //type相当于onload
    //fn相当于function(){}
    function addEvent(obj,type,fn){
        //用于保存上一个事件
        var saved=null;
        if(typeof obj['on'+type]=='function'){
            saved=obj['on'+type];//保存上一个事件
        }
        obj['on'+type]=function(){
            if(saved){
                saved();
            }
            fn.call(this);
        }
        
    }
    addEvent(window,'load',function(){
        var box=document.getElementById("box");
        //addEvent(box,'click',function(){ //目的达到,每次都执行了,没有被覆盖
        //    alert('ss');
        //});
        addEvent(box,'click',blue);
    });
    
    function red(){
        this.className="red";
        addEvent(box,'click',blue);
    }
    
    function blue(){
        this.className="blue";
        addEvent(box,'click',red);
    }
    
    //当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
    //因为积累了太多的保存的事件
    //解决方案,就是用完的事件,就立刻移除掉

    按照上面的代码出现了注释中的错误,解决的办法如下:

    //添加事件函数
    //obj相当于window
    //type相当于onload
    //fn相当于function(){}
    function addEvent(obj,type,fn){
        //用于保存上一个事件
        var saved=null;
        if(typeof obj['on'+type]=='function'){
            saved=obj['on'+type];//保存上一个事件
        }
        obj['on'+type]=function(){
            if(saved){
                saved();
            }
            fn.call(this);
        }
        
    }
    
    //当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
    //因为积累了太多的保存的事件
    //解决方案,就是用完的事件,就立刻移除掉
    
    //移除事件函数
    function removeEvent(obj,type){
        if(obj['on'+type]){
            obj['on'+type]=null;
        }
    }
    
    addEvent(window,'load',function(){
        var box=document.getElementById("box");
        //addEvent(box,'click',function(){ //目的达到,每次都执行了,没有被覆盖
        //    alert('ss');
        //});
        addEvent(box,'click',blue);
    });
    
    function red(){
        this.className="red";
        removeEvent(this,'click');
        addEvent(box,'click',blue);
    }
    
    function blue(){
        this.className="blue";
        removeEvent(this,'click');
        addEvent(box,'click',red);
    }

    二 W3C事件处理函数

    addEventListener()与removeEventListener()

    W3C事件处理函数两个,addEventListener()removeEventListener()

    //W3C自带的两个添加事件和删除事件

    1.覆盖问题,解决

    window.addEventListener('load',function(){
        alert('Lee');
    },false);
    
    window.addEventListener('load',function(){
        alert('Mr.Lee');
    },false);
    
    window.addEventListener('load',function(){
        alert('Mrs.Lee');
    },false);

    2.相同函数屏蔽的问题,解决

    window.addEventListener('load',init,false);
    window.addEventListener('load',init,false);
    window.addEventListener('load',init,false);
    function init(){
        alert('Lee');
    }

    3.是否可以传递this,解决
    例子1:

    window.addEventListener('load',function(){
        var box=document.getElementById('box');
        box.addEventListener('click',function(){
            alert(this);
        },false);
    },false);

    例子2:

    window.addEventListener('load',function(){
        var box=document.getElementById('box');
        box.addEventListener('click',blue,false);
    },false);
    
    function red(){
        this.className="red";
        this.removeEventListener('click',red,false);
        this.addEventListener('click',blue,false);
    }
    
    function blue(){
        this.className="blue";
        this.removeEventListener('click',blue,false);
        this.addEventListener('click',red,false);
    }

    4.添加一个额外的方法,会不会被覆盖,或者只能执行一次,解决

    window.addEventListener('load',function(){
        var box=document.getElementById('box');
        box.addEventListener('click',function(){
            alert('Lee');
        },false);
        box.addEventListener('click',blue,false);
    },false);

    综上所述:W3C是比较完美的解决了这些问题,非常好用,但是IE8和之前的浏览器并不支持,而是采用了自己的事件,当然IE9已经完全支持了W3C的这两个事件处理函数。

    W3C可以设置冒泡和捕获方式。

    支持W3C标准的浏览器在添加事件时用addEventListener(event,fn,useCapture)方法,基中第3个参数useCapture是一个Boolean值,用来设置事件是在事件捕获时执行,还是事件冒泡时执行。而不兼容W3C的浏览器(IE)用attachEvent()方法,此方法没有相关设置,不过IE的事件模型默认是在事件冒泡时执行的,也就是在useCapture等于false的时候执行,所以把在处理事件时把useCapture设置为false是比较安全,也实现兼容浏览器的效果。

    事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。
    事件冒泡阶段:事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。


    事件的传播是可以阻止的:
    在W3c中,使用stopPropagation()方法
    在IE下设置cancelBubble = true

    三.IE事件处理函数

    attachEvent()detachEvent()

    IE实现了与DOM中类似的两个方法:attachEvent()detachEvent()。这两个方法接受相同的参数:事件名称和函数。

    在使用这两组函数的时候,先把区别说一下:

    • 1.IE不支持捕获,只支持冒泡
    • 2.IE添加事件不能屏蔽重复的函数
    • 3.IE中的this指向的是window而不是DOM对象
    • 4.在传统事件上,IE是无法接受到event对象的,但使用了attchEvent却可以,但有些区别。

    1.覆盖问题,解决了,但有不同,结果是Mrs.Lee,Mr.Lee,最后是Lee

    window.attachEvent('onload',function(){
        alert('Lee');
    });
    
    window.attachEvent('onload',function(){
        alert('Mr.Lee');
    });
    window.attachEvent('onload',function(){
        alert('Mrs.Lee');
    });

    2.相同函数屏蔽的问题,未解决。

    window.attachEvent('onload',init);
    window.attachEvent('onload',init);
    
    function init(){
        alert('Lee');
    }

    3.是否可以传递this,不能,this指的是window。需要用call方法。

    window.attachEvent('onload',function(){
        var box=document.getElementById('box');
        box.attachEvent('onclick',function(){
            //alert(this===box);
            alert(this===window);  //true
        });
    });

    下面还有办法就是通过window.event.srcElement。代码如下:

    window.attachEvent('onload',function(){
        var box=document.getElementById('box');
        box.attachEvent('onclick',blue);
    });
    
    function red(){
        var that=window.event.srcElement;
        that.className="red";
        that.detachEvent('onclick',red);
        that.attachEvent('onclick',blue);
    }
    
    function blue(){
        var that=window.event.srcElement;
        that.className="blue";
        that.detachEvent('onclick',blue);
        that.attachEvent('onclick',red);
    }

    4.添加一个额外的方法,会不会被覆盖,或者只能执行一次,解决。

    在传统绑定上,IE是无法像W3C那样通过传参接受event对象,但是使用attachEvent()却可以。

     window.attachEvent('onload',function(){
        var box=document.getElementById('box');
        box.onclick=function(evt){ //传统方法IE无法通过参数获取evt
            alert(evt);//undefined
        }
        box.attachEvent('onclick',function(evt){
            alert(evt);//object
            alert(evt.type);//click
            alert(evt.srcElement.tagName);//DIV
            alert(window.event.srcElement.tagName);//DIV
        });
    });

    跨浏览器的兼容

    跨浏览器添加事件

    function addEvent(obj,type,fn){
        if(obj.addEventListener){
            obj.addEventListener(type,fn,false);
        }else if(obj.attachEvent){
            obj.attachEvent('on'+type,fn);
        }
    }

    跨浏览器移除事件

    function removeEvent(obj,type,fn){
        if(obj.removeEventListener){
            obj.removeEventListener(type,fn,false);
        }else if(obj.detachEvent){
            obj.detachEvent('on'+type,fn);
        }
    }

    跨浏览器获取目标对象

    function getTarget(evt){
        if(evt.target){
            return evt.target;
        }else if(window.event.srcElement){
            return window.event.srcElement;
        }
    }

    调用方式:

    addEvent(window,'load',function(){
        var box=document.getElementById('box');
        addEvent(box,'click',blue);
    });
    
    
    function red(evt){
        var that=getTarget(evt);
        that.className="red";
        removeEvent(that,'click',red);
        addEvent(that,'click',blue);
    }
    
    function blue(evt){
        var that=getTarget(evt);
        that.className="blue";
        removeEvent(that,'click',blue);
        addEvent(that,'click',red);
    }

    四.事件对象的其他补充

    relatedTarget事件

    w3c中的一个relatedTarget事件。
    例如:

    addEvent(window,'load',function(){
        var box=document.getElementById('box');
        addEvent(box,'mouseover',function(evt){
            alert(evt.relatedTarget); //得到移入box最近的那个DOM对象
        });
        
        addEvent(box,'mouseout',function(evt){
            alert(evt.relatedTarget); //从box移出最近的那个DOM对象
        });
    });

    IE提供了两组分别用于移入移出的属性fromElementtoElement,分别对应mouseovermouseout

    addEvent(window,'load',function(){
        var box=document.getElementById('box');
        addEvent(box,'mouseover',function(){
            alert(window.event.fromElement.tagName); //得到移入box最近的那个DOM对象
        });
        
        addEvent(box,'mouseout',function(){
            alert(window.event.toElement.tagName); //从box移出最近的那个DOM对象
        });
    });

    PS:fromElementtoElement如果分别对应相反的鼠标事件,没有任何意义。

    剩下要做的就是跨浏览器兼容操作:

    function getTarget(evt){
        var e=evt || window.event;
        if(e.srcElment){ //IE
            if(e.type=='mouseover'){
                return e.fromElement.tagName;
            }else if(e.type="mouseout"){
                return e.toElement.tagName;
            }
        }else if(e.relatedTarget){ //w3c
            return e.relatedTarget;
        }
    }

    屏蔽跳转操作

    取消事件的默认行为有一种不规范的做法,就是返回false

    link.onclick=function(){
        alert('Lee');
        return false;
    }

    PS:虽然return false;可以实现这个功能,但是有漏洞。
    第一:必须写到最后,这样导致中奖的代码执行后,有可能执行不到return false;
    第二:return false 写到最前那么之后的自定义操作就失效了。
    所以最好的办法应该是在最前面就阻止默认行为,并且后面的代码还可以执行。

    link.onclick=function(evt){
        evt.preventDefault;//w3c,阻止默认行为
        alert('Lee');
    }
    
    link.onclick=function(evt){
        window.event.returnValue=false;//IE,阻止默认行为
        alert('Lee');
    }

    那么跨浏览器的兼容:

    function preDef(evt){
        var e=evt || window.event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue=false;
        }
    }

    右键菜单contextmenu

    兼容:

    function preDef(evt){
        var e=evt || window.event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue=false;
        }
    }
    
    addEvent(window,"load",function(){
        var body=document.getElementsByTagName('body')[0];
        addEvent(body,'contextmenu',function(evt){
            preDef(evt);
        })
    });

    PS:contextmenu事件很常用,这直接导致浏览器兼容性较为稳定。

    卸载前事件:beforeunload

    这个事件可以帮助在离开本页的时候给出相应的提示,“离开”或者“返回”操作。

    addEvent(window,'beforeonload',function(){
        preDef(evt);
    });

    鼠标滚轮(mousewheel)和DOMMouseScroll

    用于获取鼠标上下滚轮的距离

    addEvent(document,'mousewheel',function(evt){ //非火狐
        alert(getWD(evt));
    });
    
    addEvent(document,'DOMMouseScroll',function(evt){ //火狐
        alert(getWD(evt));
    });
    
    function getWD(evt){
        var e=evt|| window.event;
        if(e.wheelDelta){
            return e.wheelDelta;
        }else if(e.detail){ //火狐
            return -evt.detail*30;
        }
    }

    PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll

    DOMContentLoaded事件和readystatechange事件

    DOMContentLoaded事件和readystatechange事件,有关DOM加载方面的事件。

  • 相关阅读:
    Java实现 LeetCode 807 保持城市天际线 (暴力)
    ASP.NET关于书籍详情和删除的Demo(HttpHandler进行页面静态化[自动生成html网页]+Entity Framework通过类创建数据库+EF删查)
    ASP.NET关于书籍详情和删除的Demo(HttpHandler进行页面静态化[自动生成html网页]+Entity Framework通过类创建数据库+EF删查)...
    ASP.NET关于书籍详情和删除的Demo(HttpHandler进行页面静态化[自动生成html网页]+Entity Framework通过类创建数据库+EF删查)...
    Java实现 LeetCode 806 写字符串需要的行数 (暴力模拟)
    Java实现 LeetCode 806 写字符串需要的行数 (暴力模拟)
    Java实现 LeetCode 806 写字符串需要的行数 (暴力模拟)
    Java实现 LeetCode 805 数组的均值分割 (DFS+分析题)
    .net web api返回结果为json
    httpclient与webapi
  • 原文地址:https://www.cnblogs.com/moqiutao/p/5257402.html
Copyright © 2020-2023  润新知