Bootstrap

javascript之setTimeout(function(){},0)

for (var i = 0;  i < 3;  i++) {

    setTimeout(function() {
         console.log(i);
    }, 0);

  console.log(i);

}

结果是:0 1 2 3 3 3


setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。



我们来简化此题:

setTimeout(function() {
        console.log(1);
}, 0);
console.log(2);
先打印2,后打印1。

因为是setTimeout是异步的。


正确理解setTimeout的方式(注册事件):
1、有两个参数,第一个参数是函数,第二参数是时间值。

2、调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。


就像我们给按钮绑定事件一样:
btn.onclick = function() {
        alert(1);
};
这么写完,会弹出1吗。不会!!只是绑定事件而已! 必须等我们去触发事件,比如去点击这个按钮,才会弹出1。 setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。
setTimeout的时间值是怎么回事呢? 比如:
setTimeout(fn, 2000)
我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。
因此setTimeout是一个约会从来都不准时的童鞋。 继续看:
setTimeout(function() {
        console.log(i);
}, 0);
var i = 1;
程序会不会报错?
不会!而且还会准确得打印1。 因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!
所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。
window.onload = function() {
        fn();
}
var fn = function() {
        alert('hello')
};
这么写,完全是可以的。因为异步!

es5中是没有块级作用域的
for (var i = 0; i < 3; i++) {}
console.log(i);
也就说i可以在for循环体外访问到。所以是没有块级作用域。
但此问题在es6里终结了,因为es6,发明了let。

这回我们再来看看原题, 原题等价于:
var i = 0;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;


因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。
原题又等价于如下的写法:
var  i  =  0 ;
console . log ( i ) ;
i ++ ;
console . log ( i ) ;
i ++ ;
console . log ( i ) ;
i ++ ;
setTimeout ( function ( )  {
     console . log ( i ) ;
} ,  0 ) ;
setTimeout ( function ( )  {
     console . log ( i ) ;
} ,  0 ) ;
setTimeout ( function ( )  {
     console . log ( i ) ;
} ,  0 ) ;

这回你明白了为啥结果是0 1 2 3 3 3了吧。

那么为什么说它是闭包, 为了很好的说明白这个事情,我们把它放到一个函数中:
var fn = function() {
        for (var i = 0; i < 3; i++) {
                setTimeout(function() {
                        console.log(i);
                }, 0);
                console.log(i);
        }
};
fn();
上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();
点击每个div都会弹出3。道理是一样的。因为alert(i)中的i是fn作用越中的,因而这是闭包。
《javascript忍者秘籍》把一个函数能调用全局变量,也称闭包。 因为全局环境也可以想象成一个大的顶级函数。
怎么保证能弹出0,1, 2呢。 解决之道: 再创建个闭包!!
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(i) {
                        return function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
或者如下的写法:
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                (function(i) {
                        divs[i].onclick = function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
因此原题如果也想setTimeout也弹出0,1,2的话,改成如下:
for  ( var  i  =  0 ;  i  <  3 ;  i ++ )  {
     setTimeout (( function ( i )  {
         return  function ( )  {
              console . log ( i ) ;
         } ;
     }) ( i ) ,  0 ) ;
     console . log ( i ) ;
}

;