首页 新闻 会员 周边 捐助

关于JS闭包的问题

0
[待解决问题]
function fn() {
    for(var i=0 ; i<2; i++) {
    //(function(){
        var backup = i;
        setTimeout(function() {
            alert(backup);
        }, 2000);
    //})();
    }
}
fn();
这个会一直输出1.我总觉得是因为for循环走的太快backup早就循环完被赋值为1后,
里面的function才输出,所以每次都是1.因为我在2000);后加了alert("a");每次都是
先弹出两次a,才开始弹出两次1.
function fn() {
    for(var i=0 ; i<2; i++) {
    (function(){
        var backup = i;
        setTimeout(function() {
            alert(backup);
        }, 2000);
    })();
    }
}
fn();
***************************************
注释去掉之后正常弹出0和1了,可是我又改成了:
***************************************
function fn() {
       for(var i=0 ; i<2; i++) {
        (function(){
           var backup = i;
           setTimeout(function() {
               alert(backup);
           }, 2000);
        })();
        alert("a");
       }
   }
   fn();
这次把我闹晕了,弹出了两次a之后,竟然先弹出了个1后才弹出个0.反着来的,
到底原理机制是什么,求大神详细讲解!
 
 
﹏℡幸福?的主页 ﹏℡幸福? | 菜鸟二级 | 园豆:202
提问于:2015-08-05 14:59
< >
分享
所有回答(2)
0

简单一句话说就是Javascript中的函数调用也是通过堆栈实现的,所以你最后例子就变成:

function fn() {
       for(var i=0 ; i<2; i++) {
        (function(){               --->
           var backup = i;
           setTimeout(function() {     | ==> 单独看成一个函数FNN()
               alert(backup);
           }, 2000);
        })();                      --->
        alert("a");
       }
 }

然后就变成了这样:

function fn() {
       for(var i=0 ; i<2; i++) {
        FNN()            
        alert("a");
       }
 }

接着js引擎执行就变成这样了:

首先是fn函数压堆栈,接着在内部碰上了FNN,那么再将FNN压堆栈,然后执行就变成这样了:

fn先执行完然后才能调用FNN,所以就造成了先alert(a)然后才是弹出FNN内部执行的结果了. 至于弹出为什么先弹出1后是0,我这边是没有这种情况的,正常输出0,1google浏览器~ 

ps: 不要让setTimeout的表象给欺骗了

visonme | 园豆:1674 (小虾三级) | 2015-08-05 15:30

需要说明下以上回答只针对你的例子,在setTimeOut调用情况下,所以不要误解成所以类似情况下(不存在settimeout)都是这样子,那就错了

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 15:33

  我的也是Chrome浏览器就是1和0,主要就是这里不懂?

支持(0) 反对(0) ﹏℡幸福? | 园豆:202 (菜鸟二级) | 2015-08-05 15:35

  还有就是闭包之后会正常弹出0和1是因为闭包的时候把var i=0和var i=1当成了两个对象地址去存储了么?

支持(0) 反对(0) ﹏℡幸福? | 园豆:202 (菜鸟二级) | 2015-08-05 15:37

@﹏℡幸福?: 

i是基本类型不是对象,所以存储上跟对象是有点区别的,如果按存储方式来分是在栈上的.

针对2,3例子说明: 

1. 闭包后输出为0,1的情况

    闭包后其实相当于(function(){})处的代码是独立函数了,backup =i 赋值也是马上在该函数上下文中生效的,所以alert后输出0,1结果是很正常的

2. 不闭包情况下,你说输出0,1情况,其实我有点奇怪,如果按我的理解应该是输出1,1才对的,因为在第一次执行i=1时候上次运行的backup = 0情况下backup已经被销毁了,而等到settimeout执行时间到时候backup应该是等于1的,所以两次应该都是1才对,除非settimeout执行的时间小于for的循环 

 

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 16:02

@visonme: 闭包输出0,1是因为都各种保存的backup值,所以两次执行settimeout不会干扰,但是非闭包情况下存在存在首次backup变量被“销毁”的情况,所以应该两次执行settimeout时候输出都为1,1

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 16:04

@visonme: 

1.我一直也把内层函数当成独立函数,只不过是引用了外层函数的变量而已。引用您“***闭包后其实相当于(function(){})处的代码是独立函数了,backup =i 赋值也是马上在该函数上下文中生效的,所以alert后输出0,1结果是很正常的***”的这句话,很多人说闭包函数马上生效。我可以证明外层函数是先执行的因为有我闭包alert("a")的代码在,是先弹出了两个alert("a"),才执行内层函数的,意思就是外层函数走完弹出两次alert之间并没有立即执行内层函数。我的外层首先走完了,backup最终只有一个值,如果没有分开记录的话i的话,内层函数怎么会记录两个值呢(虽然先弹的是1,后弹的是0)?

2.不闭包的情况是输出1和1没错。可是我在闭包的情况下加了alert("a");之后先输出1后输出了0,这个加了alert("a")和不加是反着的,为什么呢?

支持(0) 反对(0) ﹏℡幸福? | 园豆:202 (菜鸟二级) | 2015-08-05 16:19

@﹏℡幸福?: 

这其实就回到了开始回答了 “Javascript中的函数调用也是通过堆栈实现的” 

像你说的闭包函数直接执行是没有错的,但是因为你例子中用的setTimeout,setTIMEout是用于延迟执行某个函数的,所以这里在fn就变成了,alert(a) 先入栈然后才是alert(backup)入栈(在没有settimeout情况下是反过来的)

 

至于2,先输出1,后 0,这个我就不清楚了,因为我没有出现你这情况正常也不应该是1,0,你只能在查找写资料看看了

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 16:46

@visonme:  所以1中问题因为入栈的先后会出现先执行alert(a)后执行alert(backup)的情况

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 16:47

@visonme: 

 要注意函数只有在执行的时候才会进行入栈操作,setTimeout的延迟操作才导致了alert(backup)比alert(a)晚入了

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 16:48

@visonme: 嗯,首先谢谢您费心回答我问题了,可就是因为有setTimeout的延迟操作造成了alert(backup)比alert(a)晚入栈,所以内层函数没有立即执行,怎么还会记录两个值呢?我可以理解为外层和变量先入栈,这时栈里只有一个backup=1的值,之后内层函数alert(backup)才入栈么?那它既然晚入栈了怎么又会记录两个值呢?

支持(0) 反对(0) ﹏℡幸福? | 园豆:202 (菜鸟二级) | 2015-08-05 17:02

@﹏℡幸福?: 

function cb( i )
{
    var backup = i ; //因为是独立函数内部,所以自然保存有自己的backup值得
    setTimeout( function(){  
        alert(backup)
    }, 2000 ); 
}


for( var i = 0 ; i < 2 ; i++ )
{
    (function(){
        cb( i );
    })();
}

//backup是cb函数中定义的局部变量,当你将i传给cb时候,cb函数就保存了一份i的值
//并将这个值存储在局部变量backup里面了

//如果是下面就不同了
for( var i = 0 ; i < 2 ; i++ )
{
    //(function(){
        var backup = i ;
        setTimeout(function(){
            alert(backup);
        },2000);
    //})();
}

//这种情况就不一样了,因为setTimeout延迟执行的特性,导致最后在执行alert时候取了最后赋值的backup了
//其实在i = 0 时候 backup = 0
//但是当i = 1 时候,上次定义的backup已经没有意义了,因为这里重新定义了一个backup

其实刚开始对一些概念不理解的地方不需要一直咬着不放的,先一笔带过,等以后接触的知识多了,项目做多了,一些概念自然就回明白了

很多知识点在开始前都是这样模糊的,等后面自己知识面广了,一些以前的疑惑就会自然解除了

支持(0) 反对(0) visonme | 园豆:1674 (小虾三级) | 2015-08-05 17:20

@visonme: 非常感谢!

支持(0) 反对(0) ﹏℡幸福? | 园豆:202 (菜鸟二级) | 2015-08-05 17:48
0
function fn() {
       for(var i=0 ; i<2; i++) {
        (function(){
           var backup = i;
           setTimeout(function() {
               alert(backup);
           }, 2000);
        })();
        alert("a");
       }
   }
   fn();

千万不要晕,也不能晕。

1、这是一个函数闭包

2、js是单线程的

3、setTimeout会是一个异步的操作,不管时间多长也是异步,既然是异步,线程并不会等待(知道遇到alert等),会事先把两个alert('a')入栈,之后function(){alert(backup)}才会入栈,所有会出现先执行两次‘a’,再执行1,2的效果

徐大腿 | 园豆:420 (菜鸟二级) | 2015-11-06 18:00
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册