资讯专栏INFORMATION COLUMN

JS基础篇--事件绑定及深入

yearsj / 3449人阅读

摘要:学习要点传统事件绑定的问题事件处理函数事件处理函数事件对象的其他补充事件绑定分为两种一种是传统事件绑定内联模型,脚本模型,一种是现代事件绑定级模型。现代事件绑定在传统绑定上提供了更强大更方便的功能。事件和事件事件和事件,有关加载方面的事件。

学习要点:

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提供了两组分别用于移入移出的属性fromElement和toElement,分别对应mouseover和mouseout。

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:fromElement和toElement如果分别对应相反的鼠标事件,没有任何意义。

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

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加载方面的事件。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78841.html

相关文章

  • javascript知识点

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0
  • 面试宝典

    摘要:有谈谈面试与面试题对于前端面试的一些看法。动态规划算法的思想及实现方法帮大家理清动态规划的解决思路以及原理方法前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。极客学院前端练习题道练习题,面试季练练手。 由数据绑定和排序引入的几个 JavaScript 知识点 在 JavaScript 的数据绑定和做简单的表格排序中遇到的几个知识点 [[JS 基础...

    neu 评论0 收藏0
  • 深入理解js

    摘要:详解十大常用设计模式力荐深度好文深入理解大设计模式收集各种疑难杂症的问题集锦关于,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。介绍了的内存管理。 延迟加载 (Lazyload) 三种实现方式 延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。本文详细介绍了三种延迟加载的实现方式。 详解 Javascript十大常用设计模式 力荐~ ...

    caikeal 评论0 收藏0
  • 前端进阶系列-目录

    摘要:然而在最近的面试中通过学习和思考,找到了前进的方向,也得到一些大公司的录用机会。算是从初级前端毕业,进阶了吧。在这里先写个目录。赶时间的同学可以按照我的目录先自行准备提升,希望推荐文章和交流。 背景 之前我分享了文章大厂前端面试考什么?,你们一定很想看答案吧?说实话,答案我是有,在准备面试的时候会时不时翻看,但内容比较多,比较凌乱,不能指望我在一篇文章中写完。 我是从非计算机专业自学前...

    cod7ce 评论0 收藏0
  • JavasScript重难点知识

    摘要:忍者级别的函数操作对于什么是匿名函数,这里就不做过多介绍了。我们需要知道的是,对于而言,匿名函数是一个很重要且具有逻辑性的特性。通常,匿名函数的使用情况是创建一个供以后使用的函数。 JS 中的递归 递归, 递归基础, 斐波那契数列, 使用递归方式深拷贝, 自定义事件添加 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果...

    forsigner 评论0 收藏0

发表评论

0条评论

yearsj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<