闭包中循环传参问题

第一次接触这个问题还是在我刚开始学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

以上若有不足之处,欢迎指正,共同进步!

时间: 2024-11-08 11:16:30

闭包中循环传参问题的相关文章

SQL存储过程中,传参获取指定条数的记录

假设传入参数 为 @TopCount 直接写 Select top @topcount * from table 是不行的. 可以考虑使用SET ROWCOUNT 示例如下 ALTER PROCEDURE [dbo].[P_CSM_SelectHandleResult] @TopCount int=50 AS BEGIN SET NOCOUNT ON; set rowcount @TopCount SELECT * from HandleResult order by HandleIndex d

Mybatis 中在传参时,${} 和#{} 的区别

介绍 MyBatis中使用parameterType向SQL语句传参,parameterType后的类型可以是基本类型int,String,HashMap和java自定义类型. 在SQL中引用这些参数的时候,可以使用两种方式#{parameterName}或者${parameterName}. #{} #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号. 例如:order by #{parameterName} //或取Map中的value#{Key}也是一样操作. 假设传入参数是“

mybatis中#{}和${}传参的区别

使用#传入参数是,sql语句解析是会加上'',比如 select * from table where name = #{name} , 传入的name为小李,那么最后打印出来的就是 select * from table where name = '小李', 就是会当成字符串来解析,这样相比于$的好处是比较明显对的吧, #{}传参能防止sql注入,如果在mapper文件中中,你用 select * from table where name = ${name} 那么解析出来的sql就是 sel

MVC中的传参并在View中获取

首先action跳转的模式有如下: redirecttoaction("index");//一个参数时在本controller下,不传入参数. redirecttoaction(actionname,controllername) //可以直接跳到别的controller. redirecttoroute(new {controller="home",action="index"});//可跳到其他controller redirecttorou

WebForm中 页面传参的总结

1.Form表单传递(get/post)     前台: 1 姓名:<input id="TxtValue" name="TxtName" type="text" /><br /> 2 年龄:<input id="AgeValue" name="AgeValue" type="text" /><br /> 3 性别:<input ty

vue.js 1中父组件跳到子组件中并传参让子组件显示不同的内容

父组件中的点击跳转: <ul class="insurance-entry clearfloat"> <li v-link="{name:'productlist',params:{id:1}}">1</li> <li v-link="{name:'productlist',params:{id:2}}">2</li> <li v-link="{name:'product

Mybatis中#{}和${}传参的区别及#和$的区别小结

最近在用mybatis,之前用过ibatis,总体来说差不多,不过还是遇到了不少问题,再次记录下, 比如说用#{},和 ${}传参的区别, 使用#传入参数是,sql语句解析是会加上"",比如 select * from table where name = #{name} ,传入的name为小李,那么最后打印出来的就是 select * from table where name = '小李',就是会当成字符串来解析,这样相比于$的好处是比较明显对的吧,#{}传参能防止sql注入,如果

Web API中的传参方式

在Restful风格的WebApi的里面,API服务的增删改查,分别对应着Http Method的Get / Post / Delete /Put,下面简单总结了Get / Post /Delete /Put的传参方式 一.Get传参 get设计成传输小数据,而且最好是不修改服务器的数据,所以浏览器一般都在地址栏里面可以看到,数据放在url的QueryString中. (一)简单类型 $(function () { $.ajax({ type: 'get', url:'http://localh

axios中post传参方式

最近做vue项目,做图片上传的功能,使用get给后台发送数据,后台能收到,使用post给后台发送图片信息的时候,vue axios post请求发送图片base64编码给后台报错HTTP 错误 414请求一直报错,显示 request URI too large后台显示一直没有收到数据 参数为null.网上查看了很多资料,才知道axios post传参的问题. this.$axios({ method: 'post', url:url, params: { is_iso:1, goods_id: