前端js中的事件循环eventloop机制详解

前端js中的事件循环eventloop机制详解

本文主要介绍前端js中eventloop机制的相关信息。通过示例代码进行了非常详细的介绍,对大家学习或使用js有一定的参考价值。和有需要的朋友一起学习吧。

前言

我们知道js是单线程执行的,那么如何处理异步代码js呢?例如,下面的代码是如何输出的:

console . log(1);

setTimeout(function() {

console . log(2);

}, 0);

新承诺(功能(解决){

console . log(3);

resolve(date . now());

}).then(function() {

console . log(4);

});

console . log(5);

setTimeout(function() {

新承诺(功能(解决){

console . log(6);

resolve(date . now());

}).then(function() {

console . log(7);

});

}, 0);

在不运行的情况下,可以先猜测最终输出,再展开我们要说的内容。

1. 宏任务与微任务

根据我们多年编写ajax的经验,js应该是按照语句的顺序执行的。在异步的情况下,它会发起一个异步请求,然后向下执行,在异步结果返回后再执行。但是他如何在内部管理这些任务呢?

在js中,任务分为宏任务和微任务。这两个任务分别维护一个队列,都是先入先出策略执行!同步任务都是在宏任务上执行的。

宏主要包括:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js环境)。

微任务主要有:Promise.then、MutationObserver、process.nextTick(Node.js环境)。

具体操作步骤如下:

从宏任务的头部取出一个任务进行执行;

如果在执行过程中遇到微任务,将其添加到微任务的队列中;

宏任务完成后,微任务队列中是否有任务,如果有,则出去逐个执行,直到执行完毕;

GUI渲染;

返回步骤1,直到宏任务完成;

这四个步骤构成了事件的循环检测机制,我们称之为eventloop。

回到我们上面说的代码:

console . log(1);

setTimeout(function() {

console . log(2);

}, 0);

新承诺(功能(解决){

console . log(3);

resolve(date . now());

}).then(function() {

console . log(4);

});

console . log(5);

setTimeout(function() {

新承诺(功能(解决){

console . log(6);

resolve(date . now());

}).then(function() {

console . log(7);

});

}, 0);

执行步骤如下:

执行log(1)并输出1;

遇到setTimeout时,将回调代码log(2)添加到宏任务中执行;

执行console.log(3)将日志(4)添加到微任务中;

执行log(5)并输出5;

遇到setTimeout时,将回调代码log(6,7)添加到宏任务中;

执行完宏的一个任务后,检查微任务队列中是否有任务。有一个微任务日志(4)(步骤3中添加的),执行输出4;

取出下一个宏任务日志(2)执行,输出2;

宏观任务的一个任务完成后,检查微观任务队列中是否有任务,没有;

取出下一个要执行的宏任务,执行log(6),然后将log(7)添加到微任务中;

宏任务执行后,有一个微任务日志(7)(在步骤9中添加),执行输出7;

所以最后输出的顺序是:1,3,5,4,2,6,7;

我们在Promise.then中实现了一个稍微耗时的操作,这个步骤看起来会更明显:

console . log(1);

var start=date . now();

setTimeout(function() {

console . log(2);

}, 0);

setTimeout(function() {

console.log(4,date . now()-start);

}, 400);

Promise.resolve()。then(function() {

var sum=函数(a,b) {

退货数量(a)数量(b);

}

var RES=[];

for(var I=0;i5000000i ) {

var a=math . floor(math . random()* 100);

var b=math . floor(math . random()* 200);

res.push(sum(a,b));

}

RES=RES . sort();

console . log(3);

})

在Promise.then中,老师组成一个由500万个随机数组成的数组,然后对数组进行排序。运行这段代码显示,立即输出1,过一会儿输出3,然后输出2。不管等多久输出3,3之后都会输出2。这也印证了eventloop中的第三步,需要等到所有的微任务完成后,才能开始下一个宏任务。

同时,这段代码的输出非常有趣:

setTimeout(function() {

console.log(4,date . now()-start);//4,1380输出的时间差随着电脑的状态而变化。

}, 400);

本来应该是400ms后输出的,但是因为前一个任务耗时太多,后续任务只能延迟。也可以解释为setTimeout和setInterval的延迟不准确。这两种方法只能在400ms后的宏任务中使用,但具体执行时间还是要看线程是否空闲。如果前一个任务中有耗时的操作,或者涉及到无限的微任务,那么下一个任务的执行就会被阻塞。

2. async-await

从上面的代码也可以看出,Promise.then中的代码属于微服务,那么如何执行async-await的代码呢?例如,下面的代码:

函数A() {

return promise . resolve(date . now());

}

异步函数B() {

console . log(math . random());

let now=wait A();

console.log(现在);

}

console . log(1);

b();

console . log(2);

实际上,async-await只是Promise generator的一个语法上的糖。我们重写了上面的代码,使其更加清晰:

函数B() {

console . log(math . random());

答()。然后(函数(现在){

console.log(现在);

})

}

console . log(1);

b();

console . log(2);

所以我们可以理解输出的顺序:1,0,0(随机数),2,2(时间戳);18869.4084000000005

3. requestAnimationFrame

RequestAnimationFrame也属于异步执行的方法,但是我的任务既不是宏任务也不是微任务。根据MDN中的定义:

Window.requestAnimationFrame()告诉浏览器——你要执行一个动画,并要求浏览器在下次重绘前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该函数将在浏览器下一次重绘之前执行。

RequestAnimationFrame在GUI渲染之前执行,但在微服务之后执行。但requestAnimationFrame不一定要在当前帧中执行,浏览器可以根据当前策略决定执行哪一帧。

4. 总结

我们要记住最重要的两点:js是单线程和eventloop的循环机制。

好了,这就是本文的全部内容。希望这篇文章的内容对你的学习或工作有一定的参考价值。谢谢你的支持。

前端js中的事件循环eventloop机制详解