JS 函数的执行时机

87 阅读2分钟

理清JS的函数是在什么时机执行,对于理解JS函数是非常重要的。这里可以先看一个小案例:

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

这也是个经典案例了,面试笔试中经常会遇到这题或一些相关变种。

如果带着思维惯性来看,可能瞬间就得到了错误答案。这不就是打印出0到5么。

实则不然,它的正确答案是打印出 6个6 。有点懵,就让我们理一理。循环体中有了setTimeout这个函数,setTimeout又执行了一个延时的箭头函数,箭头函数 { } 中的语句并不会在第一时间执行。虽然好像等待时间设置是0,根据直觉应该是无等待直接执行的。但这点是反直觉的,即使等待时间是0,也要等到 for 循环体循环完毕,等待中的箭头函数 { } 中的语句才会执行。for 中的 i 没有声明,所以它调用的是上面声明的 i。i 已经便历了0到6( i 等于6时才会跳出 for 循环)之后,log才会执行,打出6个6。

那么问题来了,如何让for循环按照直觉来,打印出0到5,这个我们一般预期的结果呢。

很简单,略做修改即可:

for(let i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

为什么把 let 声明变量 i 挪到 for 循环的括号里面就可以了?你可以说这是因为 for 和 let 的固定搭配用法。会让 for 循环按照程序猿的直觉来行事。分析更深层的原因,是在 for 的括号内使用 let 声明,会引入块级作用域,每次循环块级作用域中的语句, i 都相互独立。每个块级作用域中的 i 是通过 i++ 递增的,只要 i 的值增一次,就会开辟一个新的作用域。在不同作用域中的 i 互不影响,所以打印出来是符合预期的0到5,只是这实现过程确实有点不符合我们的直觉。

那么除了 for let 配合使用,还有什么方法可以实现打印出0到5呢。 下面是我摸索的一个方法:

let i = 0
let arr = []
for(i = 0; i<6; i++){
  arr[i] = i
}
 console.log(arr.join('\n'))