对于javascript里的块作用域不是很理解,比如下面的这段代码为什么会输出5个6?

for(var i=1;i<=5;i++){

        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    }
阅读 4.1k
7 个回答

首先js没有严格意义的块作用域,块作用域可以用一个立即调用的函数来模拟

然后你的这段代码和块作用域无关,这是一个js闭包的问题,所谓闭包就是可以访问另一个函数作用域中变量的函数,所以这里的setTimeout是一个闭包,它在for语句所在的作用域被调用,在它内部可以访问for语句中定义的(但不是在它自己作用域内)变量i,这是因为闭包保存了包含函数的整一个变量对象

所以,i从1到5,循环体执行5次,当最后因为i变为6跳出循环时,因为闭包引用的是外部作用域的整一个变量对象,也就引用了外部的这个i,所以每次输出的这个i就是外部的i,就是6

于是就是5次6

你这不是块作用域吧,你这是异步机制。异步函数会被推入事件队列,等待主线程(执行栈)为空时才会执行。
在里面函数开始执行时,外边的for循环早循环完了。

这样写就好了,

for(var i=1;i<=5;i++){
   (function(i){
        setTimeout(function timer(){
            console.log(i);
        },i*1000)})(i);
    }
   把setTimeout立即执行。 我觉得应该是for循环执行之后才执行的timer函数
   还有js没有块作用域,是函数作用域

改成这个样子会好懂一点;只是举个例子:

var tasks = [];
for(var i=1;i<=5;i++){
   //(function(i){
   //  setTimeout(function timer(){
   //     console.log(i);
   // },i*1000);
   tasks.push(function timer(){console.log(i);});
}
//循环结束之后,依次执行推入tasks的timer函数
tasks.forEach(function(timer){
    timer();//你猜是几呢?
})

一句话:因为那五个setTimeout的回调函数是在for循环完成后被调用的,那时候i已经是6了。

更详细一点:for循环是在当前一轮的事件循环进行的,5个回调函数的执行是在未来轮的事件循环进行的。

PS:异步经常会导致代码执行顺序与代码物理位置的先后循序不一致。

  1. 不论是node环境还是浏览器下,执行js的代码只有一个线程(所谓单线程),还有个任务队列的线程(不过这是浏览器或者v8提供的,并不执行代码);

  2. setTimeout会把代码放在任务队列尾部;

  3. 不管在哪儿,都是先执行完所有js主线程的代码(即同步代码),然后再把任务队列里的代码往js主线程上丢,这也是setTimeout的原理;

  4. 所以,当你setTimeout有延迟,首先要等所有同步代码执行完,然后任务队列之前的代码执行,再是等待的时间,之后才会丢到主线程;

  5. 你这里,for是主线程的代码,先执行5次setTimeout把setTimeout里的匿名函数代码(即回调)放到任务队列,这样你任务队列就有5个待执行的任务;然后for执行完 i 此时就是6了;接着任务队列的5个代码进入主线程,开始执行。你得知道此时i已经是6了啊,所以就输出了5个6.

这是异步机制,js只有一个线程,setTimeout会在线程内的js执行完成之后,才会执行里面的代码。

    for(var i=1;i<=5;i++){
        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    }

for循环里面每一次都会执行setTimeout,因为异步机制,所以会先执行完for循环,每一个对应的setTimeout被压在队列之后。所以,for循环执行后i=6,之后setTimeout输出i。
需要注意的是,设定setTimeout(foo,time)第二个参数time的也不是说经过了time毫秒,函数就一定执行,如果线程里的js没执行完,还是需要等待。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题