事件绑定之后,会一直存在于内存中,而每一个绑定的事件,都会建立一个指向事件处理函数的连接,这种连接如果过多,会大大降低代码的质量,给浏览器很大的压力。所以,如何更优处理事件绑定,也是代码优化的一个重要环节--也就是事件委托。
本篇文章内容主要参考:“编写可维护的JavaScript”和“JavaScript高级程序设计(第三版)”。
我们都知道,当绑定的事件触发时,事件对象(event对象)会作为回调参数传入事件处理程序中,event对象中包含所有和事件相关的信息,包括:事件的宿主(target)以及事件类型等相关的信息,鼠标点击信息,键盘按键信息等。事件的优化中,事件委托就是基于event对象的信息进行定位,做出不同的逻辑,实现事件委托功能的。
一:事件的三种绑定方式:
1:直接嵌入到标签中的绑定方式(这个因为违背了松耦合的思想,是最差的绑定方法,不多说)。2:直接对象的绑定,代码如下:
function callback(){
//code
}
document.getElementById(id).onclick = callback;
这种方法大家应该很熟悉了吧,绑定事件的方法,如果需要用到event对象,那么这种写法是否依然可用?怎么说呢,你依然可以这么写,但是,在某些情况下,这会是错误的。
为了避免这个错误,有时候就不得不使用匿名函数了,比如这么写(当然,回调函数的event是可以省略的)
document.getElementById(id).onclick = function(event){callback(event);};
至于原因呢,其实就是this导致的,具体在下一个小节“event的使用”中,进行说明。
说的这里,你会不会想到一个事件优化方面的问题?匿名函数在不需要的时候,是无法被清理的,这样就会导致这个匿名函数会一直存在,影响浏览器的性能。对于这种绑定事件的方法,匿名函数也是可以被清理的,直接写
document.getElementById(id).onclick = null;
这样就把目标上的onclick事件给清理了,当然,是全部被清理了,不过这有什么关系呢,本来这种事件绑定方法就只能绑定一个回调函数嘛,清理就全部清理呗。
我觉得,这也是这种方法依然被人们认可一个因素吧,否则,这种方法除了看起来简单明了、所有浏览器支持(这个是最重要的原因吧)之外,我实在是想不到,为什么还在用这个方法了。
这些个说法都是我自己的想法,仅供参考。3:dom绑定事件的方法:
function addEvent(node,type,callback){
if(!node){return;}
if(node.addEventListener){
//W3C
node.addEventListener(type,callback,false);
}else if(node.attachEvent){
//IE8-
node.attachEvent("on"+type,callback);
}else{
//others
node["on"+type] = callback;
}
}
这种方法的特点:
1:可以绑定多个事件,在支持W3C的浏览器下,多个事件是顺序执行的,在IE8-的浏览器中,是逆序执行的。
2:如果需要event对象,有的时候,也是需要使用匿名函数进行回调的,即:callback为匿名函数。使用方法和第二种事件绑定方法一致。原因也是一致的。
3:匿名函数不可被移除。
4:为了使得各浏览器的表现一致,都使用了冒泡的方式。
5:在回调函数中的this是有区别的,W3C的浏览器,this就是触发这个事件的元素本身。表现在上述的封装函数中,this=node;在ie8-中,this表现为addEvent函数调用所在的作用域,如果是在全局作用域中使用的,那么此时的this=window。所以在这种方法的事件绑定时,要注意this的引用。二:event的使用
如果你想要看看event对象,到底包含了哪些属性或者方法,只需要在新版本的浏览器(别用ie,ie不给显示的)中执行:Object.getOwnPropertyNames(event);下面这段代码,是实现被点击的元素,背景色改变为一个随机的颜色。
<html>
<head>
<style>
<body>
<div id = "a" onclick = "myClick.handleClick()">aaaaaaaaa</div>
<div id = "b">bbbbbbbbb</div>
<div id = "c">ccccccccc</div>
</body>
<script>
//写法 1
function myChangeColor(event){
event = event || window.event;
var color = Math.round(Math.random()*1000000),
target = event.target || event.srcElement;
target.style.backgroundColor = color;
}
document.getElementById("c").onclick = myChangeColor;
//写法 2
var myClick = {
handleClick:function(event){
event = this.getEvent(event);
this.changeColor(event);
},
getEvent:function(event){
return event || window.event;
},
getRD:function(){
return Math.round(Math.random()*1000000);
},
getTarget:function(event){
return event.target || event.srcElement;
},
changeColor:function(event){
var color = this.getRD(),
target = this.getTarget(event);
target.style.backgroundColor = color;
}
};
document.getElementById("b").onclick = function(event){myClick.handleClick()};
</script>
</html>
这里,我用两种写法实现了改变被点击元素背景色的功能模块。
写法1:看起来更简单,不过,因为这个写法是把所有的东西都揉合在了一起,在功能模块单一且简单的情况下,是很简单的,可是,如果模块逻辑很复杂了之后呢?也许刚开始你写的时候,你本人逻辑很明确,功能实现也很好,可是,当需求变更之后呢?当你去负责其他的项目,这个模块由其他人维护的时候出现问题的时候呢,它要花多少时间去理解你当时的逻辑,然后才能做出修改?这种写法,使得在需求变更时,维护更加困难。
写法2:虽然现在写的代码较多,但逻辑很明确,就算是之后需求变更,我可以跟着那个绑定的接口,一步一步的调试,这样就很容易能确认问题的所在,并且在添加功能或者修改逻辑时,也更容易在哪里修改会更优,而且不需要担心是否会影响到其他地方的逻辑。这样大大的降低了维护人员的工作量,提升了团队的工作效率。这也是敏捷开发的要求,也是设计模式出现的原因之一。
说多了,要说event的使用呢。
写法 1:我绑定到了id=“c”的div,我们常用的写法,没有问题。
写法 2:我这里用了个匿名函数,为什么不直接绑定呢,就像这样
document.getElementById("b").onclick = myClick.handleClick;
这就是因为函数的本质原因了,函数就是一个指针,如上面的写法,其实就是把myClick.handleClick指向的代码以匿名函数的方式,赋值这个onclick事件,代码的表示就更明确了。
document.getElementById("b").onclick = function(event){
event = this.getEvent(event);
this.changeColor(event);
};
就是这样,想想这个时候,this代表了什么呢?很明显,this代表的是,id="b"的这个div的DOM对象,可是这个DOM对象并没有changeColor方法,所以这个地方就会出现错误。
而以匿名函数的写法呢,
document.getElementById("b").onclick = function(event){
//这里的this代表什么呢?
var that = this;
myClick.handleClick();
};
这个时候,myClick.handleClick(),就是一个局部作用域了,在这个对象内部的this,就是代表了对象myClick本身了,myClick对象内部的this和上面函数内定义的that,其实就是不同的了,这个地方的that,也是代表id="b"的这个div的DOM对象。
这就是为什么,有些事件绑定方法,必须要用匿名函数的原因,其实质,就是作用域的问题,this的问题。
所以呢,如果你想要避免这些个问题,也很简单,抛去this不用即可,比如把写法 2做如下改动:
var myClick = {
handleClick:function(event){
event = myClick.getEvent(event);
myClick.changeColor(event);
},
getEvent:function(event){
return event || window.event;
},
getRD:function(){
return Math.round(Math.random()*1000000);
},
getTarget:function(event){
return event.target || event.srcElement;
},
changeColor:function(event){
var color = myClick.getRD(),
target = myClick.getTarget(event);
target.style.backgroundColor = color;
}
};
document.getElementById("b").onclick = myClick.handleClick;
这个时候,就可以直接这样绑定了,因为,没有用到this,没有作用域的问题了。
至于刚开始说到的,减少事件绑定数目的事件委托,就只加一个例子吧,代码也就是和以上代码相差不大。
<html>
<head>
<style>
</style>
</head>
<body>
<div id = "a">
<div>aaaaaaaaa</div>
<div>bbbbbbbbb</div>
<div>ccccccccc</div>
</div>
</body>
<script>
var myClick = {
handleClick:function(event){
event = myClick.getEvent(event);
myClick.changeColor(event);
},
getEvent:function(event){
return event || window.event;
},
getRD:function(event){
return Math.round(Math.random()*1000000);
},
getTarget:function(event){
return event.target || event.srcElement;
},
changeColor:function(event){
var color = myClick.getRD(event),
target = myClick.getTarget(event);
target.style.backgroundColor = color;
}
};
document.getElementById("a").onclick = myClick.handleClick;
</script>
</html>
变动呢,首先,是html部分变动了,其次,是js部分,没有绑定需要点击的div,而是绑定了它们的父元素,这样做的好处呢,就是,把事件绑定的个数减少了,再者,以后如果再次添加兄弟元素,那么也不需要再次绑定事件了。
上述代码中,只是简单的说明了事件委托的实现方式,并没有做各种其他的处理,事件委托中,绑定事件的逻辑中,其实是很复杂的,有兴趣的朋友可以去查看事件委托方面的资料,或者再自己的项目中,使用一下,很不错的哦。
感谢查看,如果您有任何问题,或者发现文中的错误,或者描述不当,欢迎交流。