js是单线程如何模拟实现异步?event loop是怎样执行的?

165 阅读3分钟

1.同步和异步

  • 同步 两个事物相互依赖,并且一个事物必须以依赖于另一事物的执行结果。比如在事物 A->B 事件模型中,你需要先完成事物 A 才能执行事物 B。也就是说,同步调用在被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。
  • 异步 两个事物完全独立,一个事物的执行不需要等待另外一个事物的执行。也就是说,异步调用可以返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式带着调用结果再做相关事情。 比如 Java 语言中的异步都是针对有两个或者两个以上线程的程序来说的,众所周知js是单线程的,单线程的程序里它天然就是同步的。但是在js也需要一些异步执行。比如等待网络请求返回数据时,js主线程能执行其他任务。这个时候就需要通过一些机制模拟出异步的效果,也就是js执行机制event loop

2.Event loop--事件循环

在事件循环中将js任务分为微任务和宏任务注意是js任务并不是js代码

2.1微任务与宏任务

MacroTask(宏任务)

  • script全部代码、setTimeoutsetIntervalI/OUI Rendering

MicroTask(微任务)

  • Process.nextTick(Node独有)PromiseObject.observe(废弃)MutationObserver(具体使用方式查看这里

2.2事件循环与微任务和宏任务之间的关系

15fdcea13361a1ec_tplv-t2oaga2asx-zoom-in-crop-mark_1304_0_0_0.webp 如上图这是一个闭环图,注意在第一次进入整个事件循环是script全部代码这个宏任务开始的

举个例子

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>hhh</div>

</body>
<script>
    console.log('script start1');

    setTimeout(function () {
        console.log('setTimeout1');
    }, 0);

    Promise.resolve().then(function () {
        console.log('promise11');
    }).then(function () {
        console.log('promise12');
    });
    console.log('script end1');
</script>
<script>
    console.log('script start2');

    setTimeout(function () {
        console.log('setTimeout2');
    }, 0);

    Promise.resolve().then(function () {
        console.log('promise21');
    }).then(function () {
        console.log('promise22');
    });
    console.log('script end2');
</script>

</html>

浏览器打印结果

index.html:16 script start1
index.html:27 script end1
index.html:23 promise11
index.html:25 promise12
index.html:30 script start2
index.html:41 script end2
index.html:37 promise21
index.html:39 promise22
index.html:19 setTimeout1
index.html:33 setTimeout2

分析这段代码

第一轮事件循环流程分析如下

  • 上面二段script代码分别作为第一,二个宏任务进入宏任务队列,这时主线程会去执行一个宏任务,也就是第一段script代码,遇到console.log,输出script start1。

  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1

  • 遇到Promise.resolve().then,其回调函数被分发到微任务Event Queue中。我们记为Promise1

  • 遇到console.log('script end1')直接执行,输出script end1。

这个时候第一个宏任务走到头了,要去清空微任务里的任务执行console.log('promise11'),后调用then,将function () { console.log('promise12')放入微任务队列再执行console.log('promise12')。每个宏任务结束都会清空微任务队列。

script start1
script end1
promise11
promise12

第二轮事件循环开始 上面第二段script代码作为宏任务进入主线程中执行得到结果就是

index.html:30 script start2
index.html:41 script end2
index.html:37 promise21
index.html:39 promise22

第三轮事件循环开始

function () { console.log('setTimeout1'); }

执行console.log('setTimeout1')此时微任务队列为0,第三轮事件循环结束 第四轮事件循环开始

function () { console.log('setTimeout2'); 

执行console.log('setTimeout2')此时微任务队列为0,第四轮事件循环结束

3.学习路上的疑惑点:

1.宏任务是否属于异步任务?:其实这两个根本就不是同一类事物,无法进行归类的比较。能知道的是,js永远是单线程机制,就永远是同步执行。我们是利用event loop机制去实现js中的异步而已,而宏任务属于事件循环中对js任务两大分类之一

2.任务进入主线程都是同步执行的。

3.代码不是任务,所以 console.log()这句代码也不是任务,更不是宏任务