JS 函数的执行时机

1,947 阅读3分钟

前言

记录下 js 函数的执行时机,困扰了我好久。

案例

我们一个一个例子的来看

let a = 1
function fn(){
    console.log(a)
}

// 不会打印任何东西,因为函数没有执行
let a = 1
function fn(){
    console.log(a)
}
fn() // 1 
//很简单,一开始声明了 a , a 的值为1,然后调用函数 fn , 打印 a。 
let a = 1
function fn(){
    console.log(a)
}
a = 2
fn() // 2
// 一开始声明了 a , a 的值为1,然后在函数之前将 2 赋值给 a 。 所以打印出 a 的值为2。
let a = 1
function fn(){
    console.log(a)
}
fn() // 1
a = 2
// 这次是在函数执行后改变 a 的值,函数执行的时候,a 的值依旧为1.

通过上面几个例子可以看出,函数中某个变量值的判断,需要确定函数执行的时机。一开始声明的时候可能是某个值,但在函数执行的时候这个值可能已经改变了。

再来看几个 异步的例子。

let a = 1
function fn(){
    setTimeout(()=>{
        console.log(a)
    },0)
}
fn() // 2
a = 2

这个结果是为2。因为setTimeout函数会在队列任务执行完后再执行。所以等到打印 a 的时候值已经变为2了。

再来看个经典面试题:

for(var i = 0; i<6; i++){
    setTimeout(()=>{
        console.log(i)
    },0)
}
// 会打印出 六个六。

这相当于

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

因为 i 是全局的。for 循环执行完毕之后 i 的值为6。有人奇怪 i 不应该是5吗。i = 5 满足条件(<6)是最后一轮循环,然后i++,i 为6。这个循环自始至终都是在改变一个 i 的值。所以会打印6个6。

那我就是想要符合预期的打印0、1、2、3、4、5呢?可以这样:

for(let i = 0; i<6; i++){
    setTimeout(()=>{
        console.log(i)
    },0)
}
// 0 1 2 3 4 5

这里 let 会单独创建一个作用域 相当于有6个 i 。

(let i = 0) {
    setTimeout(()=>{
        console.log(i)
    },0)
}

(let i = 1) {
    setTimeout(()=>{
        console.log(i)
    },0)
}

(let i = 2) {
    setTimeout(()=>{
        console.log(i)
    },0)
};
(let i = 3) {
    setTimeout(()=>{
        console.log(i)
    },0)
}

(let i = 4) {
    setTimeout(()=>{
        console.log(i)
    },0)
}

(let i = 5) {
    setTimeout(()=>{
        console.log(i)
    },0)
};

我们还可以这样解决:

for (var i = 0; i < 6; i++) {
    setTimeout((function(i){
        return function() {
            console.log(i);
        }
    }(i)),0)
}
// 0 1 2 3 4 5

最后来看个例子,如果这个知道答案了,那基本上就懂了函数的执行时机:

function f1(){
    let a = 1
    function f2(){
        let a = 2
        function f3(){
            console.log(a)
        }
        a = 22
        f3()
    }
    console.log(a)
    a = 100
    f2()
}
f1()

这里有很多 a ,a 有很多值。我们只需要确定我们打印a的时候,a 是哪个 a 以及 a 是什么值。答案是1和22

执行 f1 函数的时候一开始第一个 a 为1,然后有个 f2 函数没执行就先跳过。然后打印 a 。所以我们可以确定打印的值为 1 。然后 a = 100 没卵用。

再然后执行 f2 函数。f2 函数中声明了 a = 2 。然后有个 f3 函数没执行就跳过。然后 a = 22。所以之前声明的那个 a 就是22。然后执行 f3 函数。打印 a 为22。就是这么简单,这就是闭包,函数和函数内部能够访问到的函数外的变量。

最后

最后肯定还有更加复杂的 setTimeout + promise + nextTick的执行顺序,涉及了宏任务跟微任务(其实非常简单)

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

这里是引用的另外一个大佬的啦,有兴趣可以戳这里哦