转载:闭包的应用场景

什么是闭包?

复制代码
function a(){
var i=0;
function b(){
alert(i);
}
return b;
}
var c = a();
c();
复制代码

全局变量c指定对 函数a的内部函数b的引用;内部函数b的执行需要依赖函数a的资源;

这里就产生一个闭包,使得a在执行完毕并返回后,不会被javascript垃圾回收机制GC回收。

因为这里c还在引用着b,而b依赖着a,故a在使用后,仍然存在于内存中。

简而言之:当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

闭包的应用场景

  1.使用闭包代替全局变量

  2.函数外或在其他函数中访问某一函数内部的参数

  3.在函数执行之前为要执行的函数提供具体参数

  4.在函数执行之前为函数提供只有在函数执行或引用时才能知道的具体参数

     5.为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点

     6.暂停执行

     7.包装相关功能

1.使用闭包代替全局变量

   全局变量有变量污染和变量安全等问题。

复制代码
//全局变量,test1是全局变量
var test1=111
function outer(){
alert(test1);
}
outer(); //111
alert(test1); //111
 
 
 

//闭包,test2是局部变量,这是闭包的目的
//我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
(function(){
var test2=222;
function outer(){
alert(test2);
}
function test(){
alert("测试闭包:"+test2);
}
outer(); //222
test(); //测试闭包:222
}
)();
alert(test2); //未定义,这里就访问不到test2
复制代码

  

2.函数外或在其他函数中访问某一函数内部的参数

   为了解决在Ajax callback回调函数中经常需要继续使用主调函数的某一些参数。

复制代码
function f1(){
var test=111;
tmp_test=function(){return test;} //tmp_test是全局变量,这里对test的引用,产生闭包
}

function f2(){
alert("测试一:"+tmp_test());
var test1=tmp_test();
alert("测试二:"+test1);
}
f1();
f2();
//测试一:111
//测试二:111
alert(tmp_test()); //111
tmp_test=null;
复制代码

  

3.在函数执行之前为要执行的函数提供具体参数

某些情况下,是无法为要执行的函数提供参数,只能在函数执行之前,提前提供参数。

有哪些情况是延迟执行?

如:setTimeOut 

     setInterval

     Ajax callbacks

     event handler[el.onclick=func 、 el.attachEvent("onclick",func)]

复制代码
//无法传参的情况
var parm=222;
function f1(){alert(111)}
function f2(obj){alert(obj)}
setTimeout(f1,500);//正确,无参数
var test1=f2(parm);//执行一次f2函数
setTimeout(f2,500);//undefined,传参失败
setTimeout(f2(parm),500);//参数无效,传参失败
setTimeout(function(parm){alert(parm)},500);//undefined,传参失败
document.getElementById("hello").onclick=f1;//正确
document.getElementById("hello").attachEvent("onclick",f1);//正确
 

//正确做法,使用闭包
function f3(obj){return function(){alert(obj)}}
var test2=f3(parm);//返回f3的内部函数的引用
setTimeout(test2,500);//正确,222
document.getElementById("hello").onclick=test2;//正确,222
document.getElementById("hello").attachEvent("onclick",test2);//正确,222
复制代码

4.在函数执行之前为函数提供只有在函数执行或引用时才能知道的具体参数 

复制代码
//动态绑定a集合的注册点击事件,在事件处理函数test1中提供参数-该点击事件的a元素本身。
var aa="ha!"
function test(obj){return function(){alert(obj);}}
var nodes=document.getElementsByTagName("a");
for(var i=0;i<nodes.length;i++){
var test1=test(aa);//由于是提前提供参数,只能提供已知的具体参数,无法事先得知点击事件的a元素本身。
//这里想提供点击事件的a元素本身作为参数宣告失败!
nodes[i].onclick=test1;//只有在注册点击事件时,才会知道该点击事件的a元素是哪个
}
 


//以下是解决方式
function associateObjWithEvent(obj,methodName){
return (function(e){
e=e||window.event;
return obj[methodName](e,this);//重点看这里!有两个参数,
        //e:event,元素绑定事件时,绑定的是对内部函数的引用,故在触发事件时,执行的是内部函数。
        //内部函数有个e参数,刚好在事件触发时,能捕获到是什么事件类型。
        //this:这里需要的就是this参数,以便当元素触发事件处理函数执行时,this=触发事件的元素本身
        //this参数无法从外部传入进来。传入进来的this都会被转化特定对象    
    );
}

function DhtmlObject(elId){
var el=document.getElementById(elId);
if(el){
//el.onclick=associateObjWithEvent(this,"doOnClick");
el.onmouseover=associateObjWithEvent(this,"doMouseOver");
el.onmouseout=associateObjWithEvent(this,"doMouseOut");
}
}
DhtmlObject.prototype.doMouseOver=function(event,element){
alert(event);//第一个参数,只在事件执行时,才知道是什么事件,这里是MouseEvent
alert(arguments[0]);//第一参数,
alert(element);//第二个参数,只在事件执行时,才知道是指代触发事件的元素本身
alert(arguments[1]);//第二个参数
}

var hello=new DhtmlObject("hello"); //执行
复制代码

另一个例子

复制代码
function associateObjWithEvent(obj,methodName){
return (function(e){
e=e||window.event;
return obj[methodName](e);
});
}

function DragListener(){
this.down=function(){
alert(this)
alert(arguments[0])
},
this.move=function(){
alert(2)
}
}

var obj=new DragListener();

document.getElementById("hello").onmousedown =obj.down;//正确 但我们在方法中用this访问到的对象是 dom
document.getElementById("hello").onmousemove = obj.move;//正确

document.getElementById("hello").onmousedown =associateObjWithEvent(obj,'down');//正确
document.getElementById("hello").onmousemove = associateObjWithEvent(obj,'move');//正确
复制代码

改进的例子,无限参数

复制代码
function associateObjWithEvent(obj, methodName){
slice = Array.prototype.slice;
var args=slice.call(arguments,2);//从第三个参数开始赋值
return (function(e){
e = e||window.event;
var array = slice.call(arguments, 0);
array.push(e); //第一个参数为event
return obj[methodName].apply(this,array.concat(args));//第二个参数,依次...
});
}


function DhtmlObject(elementId){
var el = document.getElementById(elementId);
if(el){
//el.onclick = associateObjWithEvent(this, "doOnClick");
el.onmouseover = associateObjWithEvent(this, "doMouseOver","hello2","aaa",event);//第一个参数为event,hello2是第二个参数,
//因为event只有在事件处理函数执行时才会知道是具体什么事件类型,无法通过传参提供,传参的event只能识别为null
}
}

DhtmlObject.prototype.doMouseOver = function(event){
// doMouseOver 方法体。
alert("this:"+this.id);//this:hello
alert("arg0:"+arguments[0]) ;//arg0:MouseEvent
alert("arg1:"+arguments[1]);//arg1:hello2
alert("arg2:"+arguments[2]);//arg2:aaa
alert("arg3-event:"+arguments[3]);//arg3-event:null
alert("event:"+event);//event:MouseEvent
}
//DhtmlObject("hello"); 这样写,反而出错。因为是类的写法,必须实例化才会执行。
var hello=new DhtmlObject("hello")
复制代码

5.为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点

复制代码
//假设有两个a链接,id分别为"hello"、"world"
function test(obj){
return function(){
alert(obj.id);
}
}
var nodes=document.getElementsByTagName("a");
for(var i=0;i<nodes.length;i++){
var node=nodes[i]
nodes[i].attachEvent("onclick", function(){alert(node.id)})//点击链接时,hello链接弹出world,world链接也弹出world;
//这是因为循环完毕后,node被赋值为world元素
//这不是我们预期的结果!!!

//正确写法一
// 内部参数parm为任意指定的参数,如:(function(node){return function(){alert(node.id)}})(node)
nodes[i].attachEvent("onclick",(function(parm){return function(){alert(parm.id)}})(node))
//第一次循环创建了一个闭包,缓存的node参数为hello链接。
//第二次循环又创建了一个闭包,缓存的node参数为world链接。
//点击链接时,hello链接弹出hello,world链接弹出world,因为他们调用的是各自的node参数

//正确写法二
var func=test(node);
nodes[i].attachEvent("onclick",func)
}
复制代码

6.暂停执行

出处参考:http://ljchow.cnblogs.com

这个可以做很多实用和有意思的交互。

这个是无忧上月MM的例子,用闭包实现程序的暂停执行功能,还蛮创意的。

复制代码
<input type="button" value="继续" onclick='st();'/> 
<script type="text/javascript"><!--
var st = (function() {
alert(1);
alert(2);
return function() {
alert(3);
alert(4);
}
})();
// --></script>
复制代码

把这个作用延伸下,我想到了用他来实现window.confirm。

复制代码
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
var $ = function(id) { return "string" == typeof id ? document.getElementById(id) : id; }
var doConfirm = function(divId) {
$(divId).style.display = "";
function closeDiv() { $(divId).style.display = "none"; }
return function(isOk) {
if (isOk) { alert("Do deleting..."); }
closeDiv();
}
}
</script>
<style type="text/css">
body { font-family: Arial; font-size: 13px; background-color: #FFFFFF; }
#confirmDiv { 200px; height: 100px; border: dashed 1px black; position: absolute;
left: 200px; top: 150px; }
</style>
</head>
<body>
<div>
<input name="btn2" type="button" value="删除" onclick="doConfirm('confirmDiv');" />
<div id="confirmDiv" style="display: none;">
<div style='position: absolute; left: 50px; top: 15px;'>
<p>
你确定要删除吗?</p>
<input type="button" value="确定" onclick="doConfirm('confirmDiv')(true);" />
<input type="button" value="取消" onclick="doConfirm('confirmDiv')(false);" />
</div>
</div>
</div>
</body>
</html>
复制代码

7.包装相关功能

下面的代码定义了一个函数,这个函数用于返回一个 HTML 字符串,其中大部分内容都是常量,但这些常量字符序列中需要穿插一些可变的信息,而可变的信息由调用函数时传递的参数提供。

通过执行单行函数表达式返回一个内部函数,并将返回的函数赋给一个全局变量,因此这个函数也可以称为全局函数。而缓冲数组被定义为外部函数表达式的一个局部变量。它不会暴露在全局命名空间中,而且无论什么时候调用依赖它的函数都不需要重新创建这个数组。

如果一个函数依赖于另一(或多)个其他函数,而这些其他函数又没有必要被其他代码直接调用,那么可以运用相同的技术来包装这些函数,而通过一个公开暴露的函数来调用它们。这样,就将一个复杂的多函数处理过程封装成了一个具有移植性的代码单元。

复制代码
/* 声明一个全局变量 - getImgInPositionedDivHtml -
并将一次调用一个外部函数表达式返回的内部函数赋给它。

这个内部函数会返回一个用于表示绝对定位的 DIV 元素
包围着一个 IMG 元素 的 HTML 字符串,这样一来,
所有可变的属性值都由调用该函数时的参数提供:
*/
var getImgInPositionedDivHtml = (function(){
/* 外部函数表达式的局部变量 - buffAr - 保存着缓冲数组。
这个数组只会被创建一次,生成的数组实例对内部函数而言永远是可用的
因此,可供每次调用这个内部函数时使用。

其中的空字符串用作数据占位符,相应的数据
将由内部函数插入到这个数组中:
*/
var buffAr = [
'<div id="',
'', //index 1, DIV ID 属性
'" style="position:absolute;top:',
'', //index 3, DIV 顶部位置
'px;left:',
'', //index 5, DIV 左端位置
'px;',
'', //index 7, DIV 宽度
'px;height:',
'', //index 9, DIV 高度
'px;overflow:hidden;"><img src="',
'', //index 11, IMG URL
'" width="',
'', //index 13, IMG 宽度
'" height="',
'', //index 15, IMG 调蓄
'" alt="',
'', //index 17, IMG alt 文本内容
'"></div>'
];
/* 返回作为对函数表达式求值后结果的内部函数对象。
这个内部函数就是每次调用执行的函数
- getImgInPositionedDivHtml( ... ) -
*/
return (function(url, id, width, height, top, left, altText){
/* 将不同的参数插入到缓冲数组相应的位置:
*/
buffAr[1] = id;
buffAr[3] = top;
buffAr[5] = left;
buffAr[13] = (buffAr[7] = width);
buffAr[15] = (buffAr[9] = height);
buffAr[11] = url;
buffAr[17] = altText;
/* 返回通过使用空字符串(相当于将数组元素连接起来)
连接数组每个元素后形成的字符串:
*/
return buffAr.join('');
}); //:内部函数表达式结束。
})();
/*^^- :单行外部函数表达式。*/
//输出HTML字符串
document.write(getImgInPositionedDivHtml("www.baidu.com","hello",200,100,100,40,"hello"))
复制代码
 
连接:http://www.cnblogs.com/star-studio/archive/2011/06/23/2087881.html
原文地址:https://www.cnblogs.com/sunrise/p/3962291.html