参考:JavaScript高级程序设计(第2版)第四章-作用域与第七章-匿名函数
在弄清闭包之前,先来简单了解一下执行环境和作用域。
每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的一个执行环境,在WEB浏览器中,它被认为是window对象。
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁,全局执行环境直到应用程序退出时才会被销毁。
每个函数在被调用时会创建自己的执行环境及相应的作用域链,并把作用域链赋值给内部属性([Scope])。 当代码在一个环境中执行时,会创建由变量对象构成的一个作用域链(scope chain),作用域链保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链前端,始终都是当前执行的代码所在的环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。 作用域链中的下一个变量对象来自包含它的外部环境,一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象。 标识符解析时沿着作用域链一级一级从前端逐级向后回溯搜索标识符,直至找到标识符为止。
作用域链本质上是指向变量对象的指针列表,它只引用但不实际包含变量对象。
javascript中由花括号封闭的代码块并没有自己的作用域即执行环境。
以上是执行环境和作用域的一些基本知识,现在来看闭包,闭包是指有权访问其他函数作用域中的变量的函数。
来看两个例子:
例1:
function apple (){ var num = 10; return function (){ alert(num--); } } apple()(); //10 apple()(); //10 apple()(); //10
例2:
function apple (){ var num = 10; return function (){ alert(num--); } } var showNum = apple(); showNum(); //10 showNum(); //9 showNum(); //8
在上面的例子中,匿名函数访问了外部函数apple()的变量num,它在被返回,在全局执行环境中被调用后,依然可以访问num。这是因为在匿名函数被调用时,为它创建的作用域链中包含apple()这个函数的作用域,所以它可以沿着作用域链查找到num变量。
例1中apple()()无论执行多少次弹出的值都是10,而例2中showNum()每次调用弹出值都会递减。
这是因为,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。例1就是如此,每次apple()()执行完毕后,匿名函数的作用域链被销毁,apple()函数中的所有变量都被销毁,再次执行apple()()时,访问到到的永远是初始值为10的num。 但是例2的情况有所不同,返回的匿名函数被赋值给变量showNum,在apple()被执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象,直到匿名函数被销毁。例如:设置showNum=null ,解除匿名函数的引用,随着匿名函数作用域链被销毁,其他作用域(除了全局作用域)都可以安全地销毁了。
有一个值得注意的问题,闭包只能取得包含函数中变量的最后一个值。
例3:
function createFunction(){ var fun = new Array(); for (var i =0;i<10;i++){ fun[i] = function(){ alert(i); }; } return fun; } var fun = createFunction(); for(i=0;i<fun.length;i++){ fun[i](); //10 }
在这个例子中,fun[i]()每次执行,弹出值都为10。 这是因为,在执行完createFunction()这个函数之后,i的最终值为10,在这之后每次调用匿名函数并为其创建作用域链时,引用的外部变量对象中i的值都为10;