【js基础】怎么理解javascript中的闭包?
来源:互联网 发布:远程狙击软件 编辑:程序博客网 时间:2024/04/29 04:04
表现
感觉好像打破了javascript的作用域的约束,外层的作用域可以访问里面作用域的变量;
function foo(){ var a=5; function baz(){ console.log(a); } return baz;}var f=foo();f();//5
显然,变量f是在全局作用域中,a变量在foo函数的作用域中,但是在全局作用域中却访问并输出了a的值。
分析得,执行完foo()函数时,使f指向函数baz,接着执行f()语句时,即执行函数baz(),而baz()函数得作用域中包含函数foo()得作用域,还包含全局变量得作用域,所以可以访问到a的值。
所以我称为表现,其实本质上还是,里面的作用域访问外层的作用域中的变量。
本质
上述的例子是为了展示闭包,人为地修饰代码结构,现在,我们可以看一下二个经典的闭包例子。例一取自《javascript高级程序设计》第三版,例二取自《你不知道的javascrit》上卷。原理都一样。
例一:
function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){ result[i]=function(){ return i; }; } return result; }var r=createFunctions();for(var j=0;j<r.length;j++){ console.log(r[j]());}
如果我们不认真看一眼,十分容易得出 打印 0 1 2 3 4 5 6 7 9的结论。
其实是打印10个10,为什么会这样呢?下面我从内存的角度分析一下:
当执行var r=createFunctions();这条语句时,会调用createFunctions()函数,当执行for循环例里面的语句时,首先会使result[i]指向匿名函数,
当for结束后,i此时的值为10,而此时result是一个数组,保存着10个指向匿名函数的指针(本质上就是指针),虽然这十个指针都指向同一个匿名函数,但其实指向是不同的。可以讲10个result[i]分别指向10个大小相同的不同堆内存块中,而这十个对内存块中却引用同一个值i。
用图表示如下:
返回的result里面包含十个指针,分别指向不同内存块,返回时,在执行ri,时,即执行匿名函数,匿名函数会沿着作用域找i的值,在createFunctions(),函数中找到i的值,即for循环执行完后的值,所以i=10;所以最后输出10个10。
这个和c语言中让十个指针指向同一块内存,再让其中一个指针改变一下这块内存中的内容,然后在分别输出10个指针指向的内容差不多,肯定都输出改变后的内容。只不过这里内容还要沿着作用域链找一下而已。
那么问题来了,怎么可以让其输出 0-9呢?
最简单的方法,我们使用result=(function(){})() 称为立即执行函数(IIFE),直接返回值给每个result[i],而不让他为指针,直接为值(虽然指针和值本质上也是一样的)。如下:
function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){ result[i]=(function(){ return i; })();//注意这里 // result[i]=new Function("i","return i"); } return result; }var r=createFunctions();for(var j=0;j<r.length;j++){ console.log(r[j]);//注意这里}
第二种方法:考虑到出现10个10 的本质,是因为在执行r [i] ()时,会调用匿名函数,然后沿着作用域链,在createFunctions()函数中找到i,由于for循环,i已经自增到10,所以打印10个10。通过上面分析,我们直到直到沿着作用域链找到createFunctions()的作用域里,就无法改变输出10个10 的结果。所以我们就想能不能在沿着作用域链找到createFunctions()函数的作用域之前就找到i的值呢?如下:
function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){ result[i]=function(num){ return function(){ return num; //注意这里 }; }(i); } return result; }var r=createFunctions();for(var j=0;j<r.length;j++){ console.log(r[j]());}
这里result返回数组同样包含十个指针,分别指向function(){return num;}在执行ri时,也会沿着作用域链向上找到第一个匿名函数,且时立即执行函数(IIFE),所以每次执行result[i]=function(num){}(i)时;会给num赋相应的值,所以在第一个匿名函数中就找到了num的值,即为每次传进来的i的值。
这里用可以表示为:
例二:
for(var i=1;i<=5;i++){ setTimeout(function timer(){ console.log(i); },i*1000);}
通过上面的分析,这次我们小心翼翼分析一下:当i=1时,执行setTimeout(function timer(){console.log(i);},i*1000);表示1s后才执行function匿名函数,由于javascript是单线程的语言,所以执行i++,会比匿名函数执行快很多,所以等到i变为6时,至此,已经调用5次setTimeout在javascript的任务队列中等待执行,分别需要等1s,2s,3s,4s,5s才依次执行,当第一次执行匿名函数时,i已经6,所以沿着原型链找到全局作用域中的i=6,所以输出6,易得,后面全输出6,所以结果为5个6;
那么怎么才可以输出我们期望的 1 2 3 4 5 呢?
类比上面的分析,如果不想输出6,则一定不能让i的值在全局环境中得到。那我们就要想办法再加局部环境。再延伸至全局环境中提前找到。
for(var i=1;i<=5;i++){ (function(i){ setTimeout(function timer(){ console.log(i); },i*1000); })(i);}
这样就行了,让i再第一个匿名函数的作用域中找到。注意写法。function外面的括号不能省略哦,否则就违反了js语法,若省略,在做语法分析时,会认为函数声明后面加了一个括号,这就会报错了。如果function前面再点东西,让编译器解释时,只要不认为是函数声明就行了,比如模仿jquery ,加一个+号。
应用
闭包的一个常见应用场景就是和循环在一起,比如网易云音乐的歌曲列表。
我们每点一首歌的歌名,对应的就获取列表的编号。
如果我们这样做可以吗?
var liList=document.querySelectorAll("li"); for(var i=0;i<liList.length;i++){ liList[i].onclick=function(){ alert(i); }; }
我们来分析一下,页面加载完时,for循环已经执行完了,此时在我们的内存中,i=liList.length的值,liList类数组对象保存着liList.length个指针liList[i].onclick,都指向匿名函数(其实指向是不同的,上面已经详细说明了)。当某一个li上面触发click,会执行匿名函数,会执行alert(i),在匿名函数的作用域里没找到,就沿着作用域链找到全局环境,找到了i,而此时i=liList.length。所以不管我们点击哪一个li,都会alert的i的值都为liList.length。显然不是正确数据交互。
所以我们利用立即执行函数来解决这个问题。
var liList=document.querySelectorAll("li"); for(var i=0;i<liList.length;i++){ (function(num){ liList[i].onclick=function(){ alert(num); }; })(i); }
为此我写了一个样例:
测试:点这里
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title><style> html,body{ margin:0px; padding:0px; } li{ list-style: none; } .main{ margin-top: 100px; width:600px; border: 1px solid red; background-color:pink; height: 200px; } .main>.lside,.main>.rside{ float:left; } .lside>ul>li,.rside>ul>li{ background-color: gray; opacity: 0.8; margin-top:5px; width:200px; cursor: pointer; }</style> </head><body> <div class="main"> <div class="lside"> <ul> <li>苏幕遮·碧云天</li> <li>不能怕</li> <li>九张机</li> <li>水库</li> <li>穿越火线</li> </ul> </div> <div class="rside"> <ul> <li>如果我爱你</li> <li>老大</li> <li>风去云不回</li> <li>越过山丘</li> <li>Super Tizzy</li> </ul> </div> </div></body><script> var liList=document.querySelectorAll("li"); for(var i=0;i<liList.length;i++){ (function(num){ liList[i].onclick=function(){ alert(num); }; })(i); }</script></html>
完~
- 【js基础】怎么理解javascript中的闭包?
- 怎么理解JavaScript闭包
- 【JavaScript】3.JS中的闭包之我理解
- javascript理解js闭包
- 理解JavaScript中的闭包
- 理解JavaScript中的闭包
- 理解JavaScript中的闭包
- 理解javascript中的闭包
- 理解js中的闭包
- 理解js中的闭包
- 理解js中的闭包
- 理解js中的闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- opencv中RNG产生随机数问题
- 修改系统登录相关配置
- Java Collections(0)
- 浏览器的缓存机制
- 说说 JavaScript 中那些有趣而且强大的高级函数
- 【js基础】怎么理解javascript中的闭包?
- eclipse content assist 出现错误
- oracle查询
- hdu 1142 dijkstra
- __stdcall、__cdecl 、CALLBACK 几种函数修饰符
- anaconda命令笔记
- 逻辑数据库设计
- HDU--1548-A strange lift
- Disjoint Set, 互质集合