闭包(closure)是 JavaScript 的一种语法特性。关于闭包,有一种经典的提法——“闭包是代码块和创建该代码块的上下文(环境)中数据的结合”。因为编程理论中闭包这一概念来源于数学领域,所以定义似乎有点难以接受,不过我们完全可以通俗地理解,闭包就是在函数内部定义函数,内部的函数可访问其外部函数的作用域。下面是在程序中实现闭包的例子。

function  outer(name){  // 外部的函数
  var  msg= "hello" ;
  function  inner(){  // 内部函数
    alert(msg+ " " +name);
   }
  return  inner();  // 返回内部函数
}
var  clos=outer( "WANGERN" );
clos();

 执行代码,将弹出警告“hello WANGERN”。

 

要理解闭包,还有一个很关键性概念—— JavaScript 的作用域规则。

先解释一下作用域(scope)。在运行函数都会创建属于函数的上下文环境(context)及作用域,作用域即当前环境范围内的变量。JavaScript 中最外围的环境为 window 对象,也就是全局作用域所在的环境。当执行到下一级环境时,下一级环境会主动包含上一级的作用域,最终形成一级一级关联的作用域链(对象的 [[Scope]] 属性指向该作用域链)。当有下一级环境生成时,上一级环境会失活,但不会自动销毁而保存在一种“栈”式结构中,这样可以保证作用域链的延续性,也可以环境回退时再次激活。当前环境可访问当前作用域链中的全部变量,比如上面代码中的 inner() 函数可访问 outer() 函数中的 msg 和 name 变量。

闭包就是借助这种作用域链,一方面可使内部函数可访问外部函数的变量;另一方面,闭包还可以抑制外部函数环境的销毁,使其变量始终保存在内存中,直至不需要时再销毁。

 

在实际应用中,闭包是编程中常用的技巧。下面举一些实例。 

  1. 循环绑定事件,使循环过程中的索引值 i 均有效
var  elems=document.getElementsByTagName( 'li' );
for ( var  i=0;i<elems.length;i++){
  elems[i].onclick= function (i){
    return  ( function (){  // 闭包
      alert(i);
    })
  }(i);  // 这里使用了匿名函数,实际应用中较常见
}
  1. 构造无参数的函数名引用,因为闭包的函数一般没有参数
// 继续使用之前 outer 函数的例子
var  clos=outer( "WANGERN" );
setTimeout(clos,100);
  1. 模块封装,比如在 jQuery 中就有类似的代码,保证内部变量不影响全局变量
function ($){  
   //jQuery 内部实现
})(jQuery);
// 这里使用了自动执行的匿名函数,即定义匿名函数并立即执行。看起来和闭包没什么关系,但原理是一样的。

 

接触了上面的实例,就可以很好地理解什么是闭包了。但是,需要补充一点,闭包最好要少用。之前已经说过,闭包会使变量始终保存在内存中,如果不当使用会增大内存开销,甚至会带来内存泄漏的风险。当然,还是可以使用下面的代码不能借助 GC 机制回收变量占用的内存。

// 继续使用之前 outer 函数的例子
clos= null ;