珠峰培训

闭包,事件绑定和arguments属性综合应用的小示例

作者:

2011-04-26 16:44:25

235

 在JS中,我们经常常会给window.onload多次赋值,来让网页加载完成后能够运行多个JS函数,但需要用匿名方法再包装一下,否则后者会覆盖前者,比如:

function fn(){alert(0);}

function fn1(){alert(1);}

function fn2(){alert(2);}

function fn3(){alert(3);}

window.onload=fn1//让onload加载fn1方法
window.onload=fn2//再这样赋值,由fn2会把fn1覆盖,fn1方法就不会起作用了
//我们一般会用以下的方法把fn1和fn2包装在一起来解决这个问题:

window.onload=function(){fn1();fn2();}
 

但这样写同样也会有一定的问题和风险,比如我们又给页面添加了新方法,那又得改window.onload后面的匿名方法。或者如果一个网站项目不是由一个人来完成的,而是好几个程序员来写,其它人的程序也需要这样加载,那可能别人的会把自己写的这个匿名方法覆盖掉,所以我们就会编写一个公用的页面加载方法来解决这个问题,这个方法要求整个项目组里的人来用,方法实现如下:

function bindOnce(newFn){

var oldFn=window.onload;

if(typeof oldFn=="function"){

            window.onload= function(){oldFn();newFn()}

}else{

          window.onload=newFn;

}
那这样,我们需要再次给页面加载新的方法时,直接bindOnce(fn3)既可,也不会和其它人的window.onload方法冲突。
但这个方法同样有一个局限,就是每次只能加载一个方法,如果我们有多个方法,还需要把这个方法运行多次,那可不可以写一个类似这样的方法,一次就可以给window.onload加载多个方法呢?
答案当然是肯定的,我们可以使用方法的arguments这个属性,来动态获得参数的个数,那我们就来试着写一下:

function bind(){

 

for(var i=0;i<arguments.length;i++){

var newFn =arguments[i];

var oldFn=window.onload

if(typeof oldFn =="function"){

window.onload=function(){oldFn();newFn();}

}else{

window.onload=function(){newFn();}

}

}

}
如果我们运行这个方法,当参加超过两个的时候,就会报堆栈溢出的错误(Stack overflow),所以说,上面这个方法肯定存在问题。那是什么问题呢?我们来oldFn这个变量,这个变量是引用型的,它会一直指向window.onload这个对象,当window.onload运行的时候,oldFn就是当前的window.onload(和bind方法运行到这一句的时候的情况已经不一样了,这才是最要命的,当然理解这一点也是解决问题的关键所在),所以会出现一个类似于死循环的递归。那怎么解决这个问题呢,可以用以下两种办法
一、单独定义一个方法:

function bind(){

 

for(var i=0;i<arguments.length;i++){

var oldFn=window.onload;

var newFn=arguments[i]

if(typeof oldFn=="function"){

b(newFn);//这里调用了在此方法里定义的一个b方法

}else{

window.onload=newFn; 

}

}

 

function b(newFn){

var oldFn=window.onload;

if(typeof oldFn=="function"){

 window.onload= function(){oldFn();newFn()}

}else{

window.onload=newFn; 

}

}

}

不过这个方法不够简练,我们如果用闭包来完成的话代码会更简洁,代码如下:

function perfBind(){

 

for(var i=0;i<arguments.length;i++){

var oldFn=window.onload;

var newFn=arguments[i]

if(typeof oldFn=="function"){

(function(oldFn,newFn){

 

 window.onload=function(){oldFn();newFn()}

 

 })(oldFn,newFn)

}else{

window.onload=newFn;

}

}

}

这样再用就没有问题了。
当然这里主要是用来练习基本的事件绑定、arguments用法,更重要的是学习闭包的实践应用。如果解决事件绑定,在JS里有更好的办法,就是用DOM标准的方法(在第四周第一天的课程里会详细讲),代码如下
 var EventUtil = new Object;
                EventUtil.addEventHandler = function(oTarget, sEventType, fnHandler) {
                    if (oTarget.addEventListener) {//ff
                        oTarget.addEventListener(sEventType, fnHandler, false);
                        oTarget.onclick = fnHandler;
                    } else if (oTarget.attachEvent) {//IE
                        oTarget.attachEvent("on" + sEventType, fnHandler);
                    } else {
                        oTarget["on" + sEventType] = fnHandler;
                    }
                };
                        
                EventUtil.removeEventHandler = function (oTarget, sEventType, fnHandler) {
                    if (oTarget.removeEventListener) {
                        oTarget.removeEventListener(sEventType, fnHandler, false);
                    } else if (oTarget.detachEvent) {
                        oTarget.detachEvent("on" + sEventType, fnHandler);
                    } else { 
                        oTarget["on" + sEventType] = null;
                    }
                };