JavaScript 的执行机制

前言

console.log('代码执行开始...');

setTimeout(() => console.log('定时器开始了...'));

new Promise((resolve, reject) => {
    console.log('Promise开始了...');

    for (let i = 0; i < 10; i++) {
        i === 9 && resolve();
    }
}).then(() => console.log('then开始了...'));

console.log('代码执行结束');
// outputs:
// 代码执行开始...
// Promise开始了...
// 代码执行结束
// then开始了...
// 定时器开始了...

JavaScript 是一门单线程的语言,虽然在 H5 中提出了 Web-Worker,但是 JavaScript 是单线程语言这一核心仍然没有改变,但是单线程并不意味着 JavaScript 的执行顺序取决于语句出现的顺序

JavaScript 事件循环

这个时候我们理所当然地会想到同步任务和异步任务,有了同步任务和异步任务,我们就可以导出下面的导图:

在这里插入图片描述

  1. 同步任务和异步任务分别进入不同的执行队列,同步任务进入主线程,异步任务进入 Event Table
  2. 当指定的操作完成时,Event Table 会将其回调函数注册到 Event Queue
  3. 主线程的同步任务全部执行完毕之后,去 Event Queue 读取对应的回调函数,将其加入主线程,执行
  4. 上述3个步骤会不断重复,构成了 Event Loop

JavaScript 引擎中的 monitoring process 进程会执行检查主线程执行栈是否为空,如果为空,就回去 Event Queue 中检查是否存在等待被执行的函数,并将其加入到主线程执行栈,这个操作

setTimeout

在大致了解了 JavaScript 的执行机制之后,对于 setTimeout 函数我们应该有一些新的认值

setTimeout(() => console.log('this is setTimeout function...'), 1000);
sleep(5000); // JavaScript 中并没有封装好的 sleep 函数,这里是举例

很明显,控制台并不会在 1s 后打印 ‘this is setTimeout function…’,而是会在 5s 后打印,回到JavaScript的执行机制,我们会发现其实上述代码是这样执行的:

  1. setTimeout 进入 Event Table,开始计时
  2. 主线程执行 sleep 函数,这很慢,比 setTimeout 函数要慢
  3. 1s 到了,setTimeout 计时完成,其中的箭头函数加入到 Event Queue ,但是 sleep 函数还在执行,等待
  4. 5s 到了,sleep 函数执行完毕,箭头函数从 Event Queue 中加入到了主线程,开始执行

如果 setTimeout 函数中指定的时间是 0 的话,同理,其也不会立刻执行,而是等待主线程执行栈中没有任务,再加入到主线程执行,需要注意的是,即使主线程执行栈为空,0ms 也是达不到的,最低的时间是 4ms

但问题并没有彻底解决,观察第一个代码块:

console.log('代码执行开始...');

setTimeout(() => console.log('定时器开始了...'));

new Promise((resolve, reject) => {
    console.log('Promise开始了...');

    for (let i = 0; i < 10; i++) {
        i === 9 && resolve();
    }
}).then(() => console.log('then开始了...'));

console.log('代码执行结束');

setTImeout 函数中的回调函数和我们新建的 Promise 中的 resolve 函数明显是两个异步任务,而且 setTimeout 函数语句在 resolve 函数语句之前,为何 resolve 函数会比 setTimeout 函数中的回调函数先执行呢?这时,我们需要到导入宏任务和微任务

宏任务和微任务

  • 宏任务:整体代码、setTimeout、setInterval、IO、setImmediate、requestAnimationFrame、…
  • 微任务:Promise.then catch finally、process.nextTick、MutationObserver、…

不同类型的任务会进入不同的 Event Queue,事件循环的顺序会决定 JavaScript 代码的执行顺序,进入整体代码(宏任务)之后,开始第一次 Event Loop,当前次 Event Loop 中的宏任务执行完毕之后,开始执行微任务,当前次 Event Loop 中的微任务执行完毕之后,从 Event Queue 中拉取一个新的宏任务,开始第二次 Event Loop

回到第一个代码块:

console.log('代码执行开始...');

setTimeout(() => console.log('定时器开始了...'));

new Promise((resolve, reject) => {
    console.log('Promise开始了...');

    for (let i = 0; i < 10; i++) {
        i === 9 && resolve();
    }
}).then(() => console.log('then开始了...'));

console.log('代码执行结束');
  1. 整体代码为宏任务,进入主线程执行栈,console.log(‘代码执行开始…’)
  2. 遇到 setTimeout 函数,异步宏任务,将其加入宏任务 Event Table,当指定的操作完成时,会将其回到函数加入到 Event Queue
  3. 遇到 Promise,new Promise 立即执行,console.log(‘Promise开始了…’)
  4. 遇到 resolve 函数,异步微任务,将其加入微任务 EventTable,当指定操作完成时,将其回调函数也就是 then 函数加入 Event Queue
  5. 遇到 console.log(‘代码执行结束’),当前 Event Loop 中所有宏任务执行完毕
  6. 执行当前 Event Loop 中的微任务,console.log(‘then开始了…’)
  7. 当前 Event Loop 为空,开始下一个 宏任务,console.log(‘定时器开始了…’)

完整的 JavaScript 执行机制如下:
在这里插入图片描述

后记

请分析以下代码,作为最后的练习:

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');
    });
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值