闭包,事件绑定和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;
}
}
}