js单线程、宏任务、微任务的执行顺序

1,335 阅读2分钟

单线程

单线程的介绍

js 是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作 dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。

举个例子:如果 js 被设计了多线程,如果有一个线程要修改一个 dom 元素,另一个线程要删除这个 dom 元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变!

js 是可以执行同步和异步任务的,同步的任务众人皆知是按照顺序去执行的;而异步任务的执行,是有一个优先级的顺序的,包括了 宏任务(macrotasks)微任务(microtasks)

宏任务

macrotask,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

消息队列中的等待被主线程执行的事件,macrotask执行时都会重新创建栈,然后调用 macrotask 中的函数,栈也会随着变化,macrotask 执行结束时,栈也会随之销毁。

浏览器为了能够使得JS内部 macrotask 与DOM任务能够有序的执行,会在一个 macrotask 执行结束后,在下一个 macrotask 执行开始前,对页面进行重新渲染,流程如下:

(macro)task->渲染->(macro)task->...

宏任务包含
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务

microtask,可以理解是在当前 task 执行结束后立即执行的任务,可以把 microtask 看成是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。 微任务是基于消息队列、事件循环、UI 主线程还有堆栈而来的

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个 macrotask 执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)

宏任务包含
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)

他们的执行顺序

setTimeout(function(){
    console.log(1)
});
new Promise(resolve => {
    resolve();
}).then(() => {
    new Promise(resolve => {
        resolve();
    }).then(() => {
        console.log(2);
    }).then(() => {
        console.log(3);
    });
}).then(() => {
    new Promise((resolve => {
        resolve()
    })).then(() => {
        new Promise((resolve) => {
            resolve()
        }).then(() => {
            console.log(4)
        }).then(() => {
            console.log(5)
        })
    }).then(() => {
        console.log(6);
    })
}).then(() => {
    console.log(7);
})
setTimeout(function(){
    console.log(8)
});
执行结果为
    2、3、7、4、6、5、1、8

在执行栈中,从上往下执行,遇到 promise 就推进 微任务 队列,链式调用的 then 回调执行完毕后,会将该条链的下一个 then 推入队列中,这个过程搞懂了以后是很清晰的,多少层都不用怕~~