【例1】
<script> function t1(){ var age = 20; function t2(){ alert(age); } return t2; } var tmp = t1(); var age = 99; tmp(); </script>
弹出 20
【分析】
function t1(){ var age = 20; function t2(){ alert(age); } return t2; }
在大部分的语言中,t1 被调用执行,则申请内存,并把其局部变量 push 入栈,t1 函数执行完毕,内部的局部变量,随着函数的退出而销毁,导致 age = 20 的局部变量消失。
因此,t1(); 执行完毕之后,按 C 语言的理解,t1 内的局部变量都释放了。
function t1(){
var age = 20; function t2(){ alert(age); }
return t2;
}
在 js 中,t1 执行过程中,又生成了 t2,
而从作用域上来说,t2 能访问到 age = 20,
于是 “ age = 20 ” 没有消失,而是被 t2 捕捉到了,
同时 “ age = 20 ” 与返回的 t1 函数形打了个包返回来了,成了一个 “ 环境包 ”,
这个包属于 t2,所以叫 “ 闭包 ”。
t2 把周围的环境打了包,也就是说 t2 是由自己的生态环境的,
即 即使t1 执行完毕,通过 t2 ,依然能访问该变量:这种情况,函数并非孤立的函数,甚至把其周围的变量环境,形成了一个封闭的环境包,共同返回。
一句话概括:函数的作用域取决于声明时,而不取决于调用时。
【例2】
1 function prev(){ 2 var leg = "Alexis"; 3 var arsenal_leg = function(){ 4 return leg; 5 } 6 return arsenal_leg; //前半赛季阿森纳的大腿是桑切斯 7 } 8 9 function after(){ //来到了后半赛季 10 var leg = "Giroud"; 11 var arsenal_leg = prev(); //调用prev(),arsenal_leg 函数来到了后半赛季 12 alert(arsenal_leg()); 13 } 14 after(); //谁是大腿 15 16 </script>
弹出:Alexis
【分析】:
line:14 执行 after();
line:12 alert arsenal_leg();
line:11 aesenal_leg() = prev();
line:6 return arsenal_leg();
line:3 函数声明 return leg;【真正执行的函数】
line:2 var leg = "Alexis";【作用域要从 函数声明之时寻找】
【例3】闭包计数器,多人开发 js 程序,需要一个全局的计数器
解决方案① 设立一个全局变量
window.cnt = 0;
调用 ++window.cnt
这个方案可行但是,污染了全局变量;其次比人的程序中,也可能会含有 window.cnt = ***; 则该计数器就会被损坏(所以要避免使用全局变量)
方案② 使用闭包维护一个别人污染不到的变量做技术器
<script> function counter(){ var cnt = 0; var cnter = function (){ return ++cnt; } return cnter; } var inc = counter(); alert(inc()); alert(inc()); alert(inc()); </script>
弹出 3 次,分别是:1,2,3
简化上面的程序:
<script> var inc = (function counter(){ var cnt = 0; return function (){ return ++cnt; } })(); alert(inc()); alert(inc()); alert(inc()); </script>
再改版上面的程序(inc 依然是全局变量。在工作中,一般避免全局污染或冲突):
方案 ① 统一放在一个全局对象上,如 jQuery -> $
<script> $ = {}; //模拟jQuery $.cnt = (function(){ var cnt = 0; return function cnter(){ return ++cnt; } })(); alert($.cnt()); alert($.cnt()); alert($.cnt()); </script>
方案 ② 每个人使用自己的命名空间(其实就是把自己的变量、函数都放在一个对象里)
<script> var Dee = {}; //一般用姓名做命名空间 Dee.cnt = (function(){ var cnt = 0; return function cnter(){ return ++cnt; } })(); alert(Dee.cnt()); alert(Dee.cnt()); alert(Dee.cnt()); </script>
【例4】要求:点击 4 个 li,分别弹出 0,1,2,3
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <ul> <li>阿森纳</li> <li>切尔西</li> <li>曼城</li> <li>利物浦</li> </ul> </body> <script> for(var i = 0, lis = document.getElementsByTagName("li"), len = lis.length; i < len; i++){ lis[i].onclick = (function(i){ return function(){ alert(i); } })(i); } </script> </html>
【错误的写法】(会弹出 4,4,4,4):
<script> for(var i = 0, lis = document.getElementsByTagName("li"), len = lis.length; i < len; i++){ lis[i].onclick = function(){ alert(i); } } </script>
【错误的写法分析】:
(回答者杨志):
" 这个for循环会立即执行完毕,那么当onclick触发时,inner function查找变量 i 时,会在AO+scope中找,AO中没有,scope中的变量i已经成为 link.length. ",
在运用了闭包之后,
" 这时,如果inner function被触发,他会从自己的AO以及scope(outer function的AO 和 window scope)中找寻变量i. 可以看到outer function的AO中已经包
含了i,而且对于这个for循环,会有对应有N个(function(){})() 被创建执行。所以每个inner function都有一个特定的包含了变量 i 的outer function。这样
就可以顺利输出0,1,2,3 。
结论: 我们可以看到,闭包其实就是因为Scope产生的,所以,广义上来讲,所有函数都是闭包。"
" 所谓的异步函数,是包括ajax,事件触发,setTimeout , setInterval等回调的函数。
当解析文档流时如果遇到js代码,会立即执行。如果这期间有异步函数被触发,会被存放在队列结构中。当之前的代码执行完毕后,浏览器查看此队列,如有待执行
函数,则执行;没有则等待触发。
所以,那个i 值会成为最大的那个。因为for循环在异步函数被执行前,已经跑完了。"