本文主要介绍前端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的循环机制。
好了,这就是本文的全部内容。希望这篇文章的内容对你的学习或工作有一定的参考价值。谢谢你的支持。