一句话理解
- 函数定义和函数执行是顺序关系。
function sayHi() {
console.log('hi~')
}
sayHi
问:以上代码的执行结果是什么? 答:什么都没有,因为函数只是定义了,并没有执行。
function sayHi() {
console.log('hi~')
}
sayHi();
以上才是调用。
let sayHi = function() { console.log('hi~') }
let fn = sayHi
fn();
以上代码的执行结果和 sayHi() 是一样的。fn 和 sayHi 都只是匿名函数的引用而已。
函数的执行时机不同,执行结果就不同
let a = 1
function fn(){
console.log(a)
}
fn()
以上代码的打印结果为 1,代码从上往下顺序执行:声明变量 a 并赋值,声明函数 fn (当然函数提升会将函数定义放到最上面),调用函数 fn。
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()
以上代码的打印结果为 2,执行顺序:声明变量 a 并赋值,声明函数 fn (并没有调用)(当然函数提升会将函数定义放到最上面),先改变了 a 变量的值,然后调用了函数 fn,所以打印的结果为改变后的 a 的值,如果函数的调用在 a 变量的值改变之前,那么打印的结果就为改变前的值。
下面来看一个特殊情况:
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a = 2
在函数体中定义了一个计时器,时间为 0 秒后执行并输出 a 的值。按照以上理论,打印的结果应该是 1,但实际上打印的结果是 2。
因为在 JS 中,所有的回调函数,都为异步代码。JS 代码在执行时,会优先顺序执行执行栈中的同步代码。而异步代码则会由异步处理进程放入异步队列排队,当执行栈中的同步代码全部执行完毕后,再回头来执行异步代码。而以上代码的 log 函数恰恰放在了定时器的回调函数中。
我们来搜索一下定时器的用法:
定时器可以接收一个函数作为参数,也可以接收一个字符串。
那么我们将以上代码修改一下:
let a = 1
function fn() {
setTimeout(console.log(a), 0)
}
fn()
a = 2
此时,打印的结果就为 1
我们再来看一个变态的情况:
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
打印的结果:6 个 6。
我们来看执行顺序:首先,定义变量 i 并赋值,然后进入循环。判断初始值,符合,进入循环体,发现定时器,先拿去排队。继续循环,一直到 i 的值等于 6。这时,不符合循环条件,退出循环,此时,i 的值为 6。然后再执行异步代码,也就是定时器里的回调函数,打印 i 的值:6 个 6,为什么是 6 个? 因为每循环一次就创建一个定时器,但定时器里的回调函数不执行。
for 循环的循环条件中,使用全局作用域下用 let 声明的 i 和用 var 声明的 i 的效果是一样的。
那除了在循环条件内用 let 声明 i 以外,还有没有别的方法打印出 0 ~ 5 呢?
有的,比如: 还记得定时器的语法吗?它支持参数传入一个字符串,这样就没有异步代码了。
let i = 0
for(i = 0; i<6; i++){
setTimeout(console.log(i),0)
}
另一个方法:
闭包
let i = 0
for(i = 0; i<6; i++){
!function(i){
setTimeout(()=>{
console.log(i)
},0)
}(i)
}