JavaScript 的函数在调用时存在一个调用时机的问题,在不同的时机调用会得到不同的结果,本文将举例说明调用时机对结果的影响。
- 【例】
let a = 1
function fn(){
console.log(a)
}
fn()
显然,结果会打印出1。
- 【变体1】
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()
也很显然,结果打印出2。
- 【变体2】
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2
调用函数 fn 后,再将 a 赋值为2,因此当然会打印出1。
以上例子都是显而易见的,它就是我们主观感受上的结果,但是下面的例子可能就会有些反直觉了。
- 【例】
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
}, 0)
}
fn()
a = 2
结果打印出2。setTimeout 会过一段时间再执行,在调用 fn 时,间隔一段时间再打印 a 的值,因此会先执行a = 2,接着再打印 a 的值。
- 【例】
let i = 0
for(i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
}, 0)
}
结果不是打印0、1、2、3、4、5,而是打印出6个6。理由同上,在每次执行循环体时,都会经过一段时间再打印 i 的值,JS 引擎会一直等待。等到循环结束后,i 的值为6,此时执行6次console.log(i),因此打印出6个6。
这个结果会非常的反直觉,但是将代码做微小调整,就能得到符合直觉预期的结果。
- 【变体】
for(let i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
}, 0)
}
仅将 let i = 0 移到 for 循环的圆括号中,就会打印出0、1、2、3、4、5了。这是 ES 6 新增的特性,for 和 let 结合使用时,每次循环都会创建一个新的 i,就好像每次都把 i 记录了下来。这样即使是过一段时间再执行6次console.log(i),每次都是不同的 i ,因此每次打印的值自然不同。
原因分析
事实上,第一个例子之所以会打印6个6,是作用域的问题。这里面的 i 在 for 循环的外面声明,其作用域是全局,这样当循环结束后,执行console.log(i)时,找到的 i 就是全局的 i,此时 i 的值为6,因此只能打印6个6。
要解决这个问题,就要改变 i 的作用域。
变体中,let i的作用域为 for 循环代码块,所以每一次循环中的 i 都存在于独立的作用域,即每次循环的 i 是不同的,于是每次就打出了不同的值。
除此之外,还有其他的方法也可以达到同样的效果,如:
let i = 0
for (i = 0; i < 6; i++) {
function fn(x){
setTimeout(()=>{
console.log(x)
},0)
}
fn(i)
}
每次循环都会调用 fn,将 i 的值传入并且保留在 fn 的作用域中,由于每次传入 fn 中的 i 值不同,所以每次打印出的结果自然也就不同了。