理解js事件循环(event loop)

128 阅读3分钟

1、JS异步编程基本概念

js之所以是单线程的是因为浏览器只会分配一个线程来执行js代码,之所以分配一个线程是因为浏览器考虑到多线程操作会导致一些问题,假设js是多线程的,其中一个线程在DOM节点上添加内容,而另一个线程在这个节点上删除内容,那么浏览器该执行哪一个呢?所以js的设计只能是单线程的。

但是单线程会造成很多的任务都需要等待执行,所以就引入了浏览器的事件循环机制

1.1 进程和线程Tip

进程中可以包括多个线程,比如打开一个页面,这个页面就占用了计算机的一个进程,页面加载时,浏览器会分配一个线程去计算DOM树,一个线程去执行js代码,其他的线程去加载其他的资源文件等。

2、 event loop

js主线程不断地循环往复的从任务队列中读取任务,执行任务,这种运行机制称为事件循环(event loop)

2.1 宏任务和微任务

浏览器的事件循环(event loop)中分为宏任务和微任务。js中任务分为同步任务和异步任务。

2.1.1 宏任务(macro task)

js中主栈执行的大多数的任务,例如:定时器事件绑定ajax回调函数node中的fs操作模块等就是宏任务。

2.1.2 微任务(micro task)

promiseasync/awaitprocess.nextTick等就是微任务。

2.1.3 为什么要引入微任务,只有宏任务不可以吗?

微任务的引入是为了解决异步回调的问题,假设只有宏任务,那么每一个宏任务执行完后回调函数也放入宏任务队列,这样会造成队列过长,回调的时间变长,这样会造成页面的卡顿,所以引入了微任务。

2.1.4 为什么await后面的代码会进入到promise队列中的微任务?

async/await 只是操作promise的语法糖,本质还是promise;举一个例子:

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
// 上面的代码等价于 ==>
async function async1() {
    console.log('async1 start');
    Promise.resolve(async2()).then(() => {
        console.log('async1 end')
    })
}

3、 宏任务和微任务的执行顺序(很重要!)

主栈队列就是一个宏任务,每一个宏任务执行完就会执行宏任务中的微任务,直到微任务全部都执行完,才开始执行下一个宏任务。

js中任务的执行顺序优先级是:主栈全局任务(宏任务)> 宏任务中的微任务 > 下一个宏任务。所以promise(微任务)的执行顺序优先级高于setTimeout定时器。

不能没有目的的将.then的回调放入微任务队列;因为没有调用resolve或者reject之前是不算异步任务完成的,所以不能将回调随意的放入微任务事件队列。

await是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到micro task中,然后就会跳出整个async函数来继续执行后面的代码。

process.nextTick是一个独立于eventLoop的任务队列,主栈中的宏任务每一次结束后都是先执行process.nextTick队列,再执行微任务primise.then()

每一个宏任务和宏任务的微任务执行完后都会对页面UI进行渲染。