来源:http://www.108js.com/article/article1/10177.html?id=899

第一次接触这个问题还是在我刚开始学js的时候,当时就是一头雾水,时隔一年多了,突然又想起了这个问题,在这个春气盎然的周末,我就坐下来研究下并把结果和大家分享下;

先看代码:demo.html <!DOCTYPE HTML>  <html>   <head>    <meta charset="gbk"/>    <title>闭包循环问题</title>    <style type="text/css">      p {background:red;}    </style>  </head>   <body>    <p id="p0">段落0</p>    <p id="p1">段落1</p>    <p id="p2">段落2</p>    <p id="p3">段落3</p>    <p id="p4">段落4</p>  <script type="text/javascript">   for( var i=0; i<5; i++ ) {     document.getElementById("p"+i).onclick=function() {       alert(i); //访问了父函数的变量i, 闭包     };  }; </script>  </body>  </html>
  每次循环就为对应的编号段落上添加一个click事件,事件的回调函数是执行一个alert();如果你以前没这么用过的话,估计也会认为单击某个段落就会弹出这个段落相应的编号0,1,2,3,4。但实际上是都是弹出5;

网上已经有很多讨论的博客了,他们给出了很多方法去实现弹出对应的编号。比较易于理解的方法如下:

1,将变量i保存在对应的段落的某个属性上点击查看效果。

     var pAry = document.getElementsByTagName("p");
for( var i=0; i< 5; i++ ) {
pAry[i].no = i;
pAry[i].onclick = function() {
alert(this.no);
}
};

2,加一层闭包,i 以函数参数形式传递给内层函数点击查看效果。

 ~function test() {
var pAry = document.getElementsByTagName("p");
for( var i=0; i< pAry.length; i++ ) {
(function(arg){
pAry[i].onclick = function() {
alert(arg);
};
})(i);//调用时参数
}
}();

当然还有其他一些方法,但是都不太好理解。     而我要探索的是,为什么demo.html中的返回值始终是5。网上的说法是“变量i是以指针或者变量地址方式保存在函数中”,因为只有按照这样理解,才能解释。可是仅仅凭借一个结论怎么才能服众了?

谈到指针或者变量地址这个话题,在C语言中倒是家常便饭了,但是在js这么性感的语言中,除了对象的及其对象属性的引用之外很少用到。一个基本的数据类型居然和指针拉上关系了,这勾起了探索的欲望。

3,试试下面的代码 点击查看效果

~function test() {
var temp =10;
for( var i=0; i< 5; i++ ) {
document.getElementById("p"+i).onclick=function() {
alert(temp); //访问了父函数的变量temp, 闭包
}
};
temp=20;
}();

它的执行结果是每个段落的弹出都是20,而不是10。说明当我们在单击的那个时候,temp的值已经是20。这是个似乎不需要我来说明,很显然的结果,因为在我们单击之前,temp已经被赋值为20了。

4,再看看下面的代码  点击查看效果。
我们在temp被改变值之前有程序去触发单击事件,弹出的是10;

 ~function test() {
var temp =10;
for( var i=0; i< 5; i++ ) {
document.getElementById("p"+i).onclick=function() {
alert(temp); //访问了父函数的变量i, 闭包
}
if(i===1){
var evt = document.createEvent("MouseEvents");
evt.initEvent("click",true,true);
document.getElementById("p1").dispatchEvent(evt);
}
};
temp=20;
}();

这说明我们在p1上绑定的单击事件回调函数本来是要返回10的,当我再次手动去单击p0段落时,弹出20的原因是因为temp的值改变了。也就说明,每次弹出时,访问到的是temp此刻的值,而不是绑定时候的值;这可以说明变量temp确实是以变量地址保存的。扩展开去就是:函数内部访问了与函数同级的变量,那么该变量是常驻内存的。访问该变量实质上是访问的是变量的地址;

通过以上的结论,那么我们可以简单的描述闭包的本质了:在子作用域中保存了一份在父级作用域取得的变量,这些变量不会随父级作用域的销毁而销毁,因为他们已经常驻内存了!

这句话也就说明了闭包的特性了: 1:因为常驻内存所以会造成内存泄露 2,只要其他作用域能取到子作用域的访问接口,那么其他作用域就有方法访问该子作用域父级作用域的变量了。

看这样一典型的闭包的例子:   function A(){    var a=1;
   function B(){     return a;   };    return B;  };
 var C=A();//C取得A的子作用域B的访问接口  console.log(C());//1 C能访问到B的父级作用域中的变量a 
以上若有不足之处,欢迎指正,共同进步!

彻底弄懂js循环中的闭包问题的更多相关文章

  1. js进阶 12-2 彻底弄懂JS的事件冒泡和事件捕获

    js进阶 12-2 彻底弄懂JS的事件冒泡和事件捕获 一.总结 一句话总结:他们是描述事件触发时序问题的术语.事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件.相反的,事件 ...

  2. Handlebars.js循环中索引(@index)使用技巧(访问父级索引)

    使用Handlebars.js过程中,难免会使用循环,比如构造数据表格.而使用循环,又经常会用到索引,也就是获取当前循环到第几次了,一般会以这个为序号显示在页面上. Handlebars.js中获取循 ...

  3. 关于for循环中的闭包问题

    还是昨天的那个简单的小项目,已经花了一天的时间了 - - .从&&的用法,到CSStext,到今天马上要谈的闭包(closure),通过一个小东西,真真发现了自己的各方面不足.昨天发完 ...

  4. JS循环中使用bind函数的参数传递问题

    JS循环中使用bind函数的参数传递问题,问题代码如下: for (var sc in result) { var tempp = '<div class="sidebar_todo_ ...

  5. 一文弄懂js的执行上下文与执行上下文栈

    目录 执行上下文与执行上下文栈 变量提升与函数提升 变量提升 函数提升 变量提升与函数提升的优先级 变量提升的一道题目引出var关键字与let关键字各自的特性 执行上下文 全局执行上下文 函数(局部) ...

  6. js代码中的闭包

    作为一个后台开发人员了解前端非常重要,尤其是深处学校实验室做项目时前端把写好的代码直接给你,然后你在修改的时候.我经常做的就是修改前端的代码的HTML和后台交互的部分以及js的ajax部分,之后修改之 ...

  7. 彻底弄懂JS的事件冒泡和事件捕获(不推荐阅读)

    由于搬去敌台了,好久没来博客园,今天无意中翻到有“误认子弟”的评论,这里特意做个说明. 本文中关于事件冒泡和事件捕获的描述和例子都是OK的,错就错在后面用jquery去展示了利用事件冒泡的例子有误,其 ...

  8. js循环中使用async/await踩过的坑

    最近写koa的时候遇见需要在循环中使用async/await的情况,当然第一反应就是直接上forEach,然后就直接翻车了... 直接上代码: function handleSql(val) { re ...

  9. 让你弄懂js中的闭包

    目录 闭包 闭包如何产生 闭包是什么 常见的闭包 闭包的作用 闭包的生命周期 闭包的应用 闭包的缺点 内存泄露 内存溢出 闭包面试题 闭包 之前在我执行上下文执行上下文栈这篇文章中,出现了这样一个题目 ...

随机推荐

  1. git代理设置方法解决

    git config --global https.proxy http://127.0.0.1:1080 git config --global https.proxy https://127.0. ...

  2. 使用dd命令克隆整个系统(转)

    神奇的ghost的原理是什么呢?不就是数据复制吗?Linux下的dd命令不就是最强大的数据复制工具!          既然如此,我为什么要使用g4l这样复杂的工具呢?一条dd命令不就可以帮我实现任意 ...

  3. Libcurl多线程crash问题(cento)

    cento :http://blog.csdn.net/delphiwcdj/article/details/18284429 1 问题背景 后台系统有一个单线程的http接口,为了提高并发处理能力, ...

  4. greendao操作数据库的使用方法

    第一步:把greendao-1.3.0-beta-1,greendao-generator-1.3.1两个jar包加载到工程的lib的文件夹中,一定要右键点击Add As Library后才能使用. ...

  5. SQLSERVER进程CPU使用率100%

    解决SQLSERVER2008 CPU使用率99%: 1.dbcc checkdb 重建索引 2.檢查有沒有死鎖 -- sp_lock SELECT request_session_id spid , ...

  6. 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()

    在子线程中new一个Handler为什么会报以下错误? java.lang.RuntimeException:  Can't create handler inside thread that has ...

  7. linux中的chage命令

    在LINUX系统上,密码时效是通过chage命令来管理的. 参数说明:-m 过多少天后可修改密码.为0时代表任何时候都可以更改密码.-M 过多少天后密码过期.-W 用户密码到期前,提前收到警告信息的天 ...

  8. Windows XP SP3下成功编译CUint2.1-3

    软件环境:Visual C++ 6.0 操作系统:Windows XP SP3 从sourceforge.net下载CUint-2.1-3.tar.bz解压出目录CUnit-2.1-3 从CUint打 ...

  9. Source Insight 3.X utf8支持插件更新

    [更新内容] 修复了当UTF8文件外部改变时,SI无法检测到的bug. 实现 [下载地址] 点我 [计划] 未来(无限长)优化utf8编码检测规则,提高准确度.

  10. JAVA XML

    为什么要用XML:1各平台之间程序很难直接交流 2各程序之间交换数据 xml定义  可扩展标记语言,标准通用标记语言的子集,一种用于标记电子文件使其具有结构性的标记语言. 它可以用来标记数据.定义数据 ...