事件绑定分为两种:
- 传统事件绑定(内联模型,脚本模型)
- 现代事件绑定(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。
传统事件绑定的历史问题
传统事件绑定中内联模型基本很少使用不必了解。所谓脚本模型就是将一个函数赋值给一个事件处理函数。
<script>
window.onload=function(){
alert("Bert");
}
</script>
打开网页会弹出提示框“Bert”。这是最常见也是最基本的事件绑定,但是存在很多问题!
问题一
一般HTML文档中会引入多个JS文件,按顺序引入时对相同元素进行操作时会发生冲突,或者两个<script>代码中不同操作也会有一样的问题。譬如,上述的例子中在第一个<script>下增加一个相同的<script>弹出的信息为“Tom”,最终效果会发现打开网页会弹出提示框“Tom”,之前的<script>代码被覆盖了。
解决问题一
正因为第一个要执行的时间会失效,所以需要增加一个判断之前是否有window.onload这样一个事件,并且创建一个“保存器”用来存储第一个会失效的事件,之后再第二个执行事件中去调用这个判断来执行第一个事件。说的自己都绕了,还是代码展示形象一些。
<script>
window.onload=function(){
alert("Bert");
}
if(typeof window.onload=="function"){
var saved=null;
saved=window.onload;
}
</script>
<script>
window.onload=function(){
if(saved) saved();
alert("Tom");
}
</script>
saved就是window.onload,saved()相当于window.onload(),但是window.onload()是不能执行的,所以saved()相当于window.οnlοad=function(){}。增加一个这样判断之后,在页面中可以正常的先弹出“Bert”,再弹出“Tom”。
问题二
这个问题是涉及到事件切换器,首先来看一下事件切换器,
结构和JS:
<div id="test">测试</div>
<script>
function toRed(){
this.className="red";
this.onclick=toBlue
}
function toBlue(){
this.className="blue";
this.onclick=toRed;
}
var box=document.getElementById("test");
box.onclick=toRed;
</script>
CSS:
.red{
width: 50px;
height: 80px;
line-height: 80px;
color: blue;
background-color: red;
}
.blue{
width: 80px;
height: 50px;
line-height: 50px;
color: red;
background-color: blue;
}
这最终的结果就会在点击“测试”区域时,该区域会在“red”样式和“blue”样式之间来回切换。
首先,当在切换颜色事件之前想要增加一个弹窗时,会发现点击之后没有弹窗,因为被之后的改变颜色事件覆盖了,这也就是之前提到的第一个的覆盖问题。
var box=document.getElementById("test");
box.onclick=function(){
alert("开始事件切换咯!")
}
box.onclick=toRed;
如果想要解决上述问题,除了解决问题一提供的麻烦办法之外,还可以将两个事件包含在一个匿名函数中,但这就带来了第二个问题,this的指向问题。
var box=document.getElementById("test");
box.onclick=function(){
alert("开始事件切换咯!")
toBlue();
}
但这时就只会出现弹窗而不会改变颜色,是因为通过匿名函数执行某一函数,里面的this就跳出了作用域,即代表window。若解决this指向问题可通过call来传递this。
toBlue.call(this);
最后,this指向问题解决了又带来了新问题,弹窗只弹出一次的问题。跳转到“toBlue”函数,其中的onclick改变成红色事件又会覆盖弹窗事件。
解决问题二
综上问题二中的诸多问题,可通过创建一个自定义的事件处理函数,来解决问题。添加事件函数,obj相当于window,type相当于onload,fn相当于functio(){}。
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); //把this传递进去
};
}
addEvent(window,"load",function(){
var box=document.getElementById("test");
addEvent(box,"click",function(){
alert("改变颜色咯!");
});
addEvent(box,"click",toBlue);
});
function toRed(){
this.className="red";
addEvent(this,"click",toBlue);
}
function toBlue(){
this.className="blue";
addEvent(this,"click",toRed);
}
就此,可以实现点击一次出现一次弹窗,改变颜色。但是当你单击很多很多次切换后,浏览器直接卡死,或者弹出一个错误:too much recursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的移除,导致越积越多,最后卡死。可以添加一个删除事件处理函数来解决。
function removeEvent(obj, type) {
if (obj['on'] + type) obj['on' + type] = null; //删除事件处理函数
}
function toRed(){
this.className="red";
removeEvent(this,"click");
addEvent(this,"click",toBlue);
}
function toBlue(){
this.className="blue";
removeEvent(this,"click");
addEvent(this,"click",toRed);
}