《JS 函数的执行时机》

41 阅读3分钟

先给大家展示两组代码吧

let i 
for(i = 0; i<6; i++){
 setTimeout(()=>{   
 console.log(i)   
},0)
 }
 结果是打印出 666666
for(let i = 0; i<6; i++){  
 setTimeout(()=>{    
 console.log(i) 
 },0) }
结果是打印出 012345

规则:同步优先、异步靠边、回调垫底。

  • js的执行机制: js是单线程环境,从上到下、依次执行,即 同步执行;js在执行代码的过程中,碰到同步代码会依次执行,碰到异步代码就会将其放入任务队列中进行等待,当同步代码执行完毕后再开始执行异步代码,即 异步执行。
  • js作用域问题:当同步代码执行完毕后,开始执行异步的setTimeout代码,执行setTimeout时需要从当前作用域内寻找一个变量i,此时for循环已执行完毕,当前 i=6,所以执行setTimeout时输出为6,任务队列中的剩余5个setTimeout也依次执行,输出为6。
  • 代码1中的let在for循环的外部。而在代码2中,let只在代码块内才有效,let只能声明一次。变量i是用let声明的,当前的i只在本轮循环中有效,每次循环的i 其实都是一个新的变量,所以setTimeout定时器里面的i,其实是不同的变量,即最后输出0-5。(若每次循环的变量i都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环值)

上面代码1执行的结果是6个6,你或许会好奇为什么不是0-5呢?,这里要说一下,当代码执行到setTimeout,setTimeout是一个定时器它会告诉计算机我会尽快执行,具体的时间不确定,也可以认为是在你for循环完毕我在执行,而在for循环执行完毕时此时的i=6然后再执行setTimeout后的代码,由于for循环了6次,也就是说定时器会执行6次,此时i=6,所以打印出6个6。

比较代码1和代码2你会发现,为什么let换了一下地方就变成了0-5了,这是为什么呢?同样是在for循环时当碰到setTimeout定时器它也会跳过,不过此时let做了一个操作就是,每执行一次就重新创建一个i,执行几次就创建并全部保存下来,关于调用栈的原理,每个i都被压栈到调用栈中,在setTimeout执行时,在弹栈出来,只就是它的原理。你可能会疑惑为啥代码2和代码3代码基本相同,不同的是一个是一个是let,一个是var为啥结果不一样。那是因为同一作用域let只能声明一个同一变量名称的变量,他不得不通过调用栈把它们存起来,而var会直接覆盖。


转载于JavaScript函数的执行时机 - 知乎 (zhihu.com)