事件循环中的同步和异步以及宏任务、微任务

事件循环中的同步和异步以及宏任务、微任务,第1张

同步和异步 首先我们要明确一点,就是同步和异步方法与宏任务微任务没有直接必然联系,这是很多新手初期理解的误区;你可以简单理解为同步按顺序执行,异步等待同步执行完成后再执行。异步简单理解就是通过一个子线程来完成任务,且独立于主线程,但它有个局限,就是我们无法确定主线程代码何时结束,也就是说我们无法确定其子线程回调函数入任务队列的时间,无法确定何时开始执行(拓展:这就是为什么在强制同步事件中,要用requestAnimationFrame取代setTimeout方法的原因)​​​​​​以下代码会清晰的展示异步函数的局限性。根据运行结果看出,此回调并不是一秒后就立即执行(当然,如果同步代码能在一秒内执行完毕,回调也会立即执行)此局限性有利有弊,后续会通过宏任务与微任务的概念进行梳理
setTimeout(()=> {
    console.log('setTimeout 1000ms')
},1000)
let i = 0;
let start = Date.now();
function count() {
    // 做一个繁重的任务
    for (let j = 0; j < 1e9; j++) {
        i++; 
    } 
    console.log("Done in " + (Date.now() - start) + 'ms');
}
count()
/*
第一个方法: 异步函数,回调会在1秒后执行
第二个方法: count函数会在1.5秒后执行完毕
结果:
Done in 1555ms
setTimeout 1000ms
*/
宏任务和微任务 宏任务与微任务有哪些

宏任务

 script(整体代码)setTimeout/setInterval/setImmediate回调I/O *** 作(ajax)用户事件触发(click/onmousemove...)

微任务

Promise.resolve().then(...)           Vue源码中关于vdom使用的 *** 作MutationObserver process.nextTick(Node.js)

这是我将宏任务与微任务与同步异步概念分开的原因,因为异步可能是宏任务也可能是微任务,而宏任务可能是异步代码也可能是同步代码,所以如果强行将两者结合理解,会造成不必要的理解障碍。

事件循环与宏/微任务 相比于同步异步方法,宏任务和微任务能更好的描述事件循环,可以通过此网站更好地理解宏任务与微任务,神站也。浏览器的渲染机制是,在当前宏任务执行完成之前,会清空所有微任务,并在下一个宏任务开始之前进行渲染 *** 作。

 以下代码运行和解析可以帮助你更好地理解事件循环:

$('#progress')[0].innerHTML = 'script 1';
setTimeout(()=>{
	$('#progress')[0].innerHTML = 'setTimeout 2';
}, 3000)
Promise.resolve().then(function () {
    $('#progress')[0].innerHTML = 'script 2';
})
setTimeout(()=>{
	$('#progress')[0].innerHTML = 'setTimeout 1';
}, 2000)

1. script宏任务入任务队列,首先执行dom *** 作,但因为此时script宏任务还未执行完毕,并未渲染

2. setTimeout执行,回调将会在3秒后以宏任务状态入任务队列

3. Promise执行,then回调入微任务队列

4. setTimeout执行,回调将会在2秒后以宏任务状态入任务队列

5. script宏任务即将结束,即将清空微任务队列

6. 清空微任务队列,执行dom *** 作

7. script宏任务执行结束,以script 2渲染

8. 从任务一开始的2秒后 第二个setTimeout回调推入任务队列-宏任务,此时无任何微任务和其他响应事件,直接运行,以setTimeout 1渲染

9. 结束第二个setTimeout回调的宏任务

10. 从任务一开始的3秒后 第一个setTimeout回调推入任务队列-宏任务,此时无任何微任务和其他响应事件,直接运行,以setTimeout 2渲染

11. 结束所有事件

事件循环初步解析

我们知道,以下代码中的dom是不会实时跟随js *** 作更新的,只会在js循环结束后进行更新渲染。

setTimeout(()=>{
    console.log(123)
})
let start = Date.now();
// 假设这是个耗时比较长的 *** 作
for (let i = 0; i < 1e5; i++) {
    $('#progress')[0].innerHTML = "Now is " + (Date.now() - start);
}

因为for循环做为script同步代码,必须等待所有 *** 作结束才能执行下一个任务,假设此时运行一个耗时很长的复杂任务,这时候又接连产生其他任务如:用户事件 *** 作、异步回调等,这些其他任务会被排入队列,等待引擎处理完复杂任务之后,再根据先进先出原则继续处理事件。那么上述代码处运行程为:

1. 首先script宏任务入队列,setTimeout回调函数作为另一个宏任务在经过回调时间后(图中代码为0s,但是一般有4ms延时)立即排入队列,但是排在script任务后面

2. 继续执行后续script任务,在执行大概1000ms后结束任务

3. script任务执行完毕之后,浏览器进行渲染 *** 作

4. 运行setTimeout回调,打印日志

那么我们将如何通过利用宏/微任务来解决上述问题并延伸应用场景?

微任务/宏任务应用场景

宏任务

任务拆分

如下代码所示,count方法总运行时间为循环1e7的运行时间,通过setTimeout拆分成若干以1e4循环时间为单位等分。于是在每个1e4时间间隔内,我们都可以通过穿插宏任务来执行我们想要进行的 *** 作。(穿插的宏任务分为主动类型,如用户点击响应事件等,被动类型,如setTimeout在经过一段事件后的入队回调函数等。)

setTimeout(()=> {
    alert('setTimeout')
},1000)
let i = 0;
function count() {
    do {
        i++; 
		$('#progress')[0].innerHTML = i;
	} 
	while (i % 1e4 != 0);
	if (i < 1e7) {
		setTimeout(count)
	} 
}
count()
进度条显示

 上述代码中,会以每1e4循环时间为单位对$('#progress')进行一次渲染,那我们就可以根据我们所需要的等分条件进行 *** 作dom进度条显示。注意,我们之前提到,setTimeout有大概4ms的延时,所以我们等分单位越小,时间会越长;当然我们也可以稍微优化下将setTimeout提前至count方法开始处。

微任务

Vue源码中对于渲染更新的应用

vue会在浏览器渲染之前,统一处理所有的 *** 作,一次性生成虚拟dom后再转为真实dom

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/web/1297452.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-10
下一篇2022-06-10

发表评论

登录后才能评论

评论列表(0条)

    保存