聊一下JS执行原理。关于事件循环(Event Loop)、微任务、宏任务

857 阅读4分钟

单线程的JavaScript

JavaScript,自诞生以来就是单线程。作为Web脚本语言,它的主要用途就是为结构和样式提供行为以及操作DOM。它独有的语言性质决定了:Javascript只能是单线程,否则会带来很复杂的同步问题。

例如:两个线程同时在一个DOM节点上进行操作,那么这时浏览器应该以哪个线程为准呢?为了避免这些问题,从一开始,Javascript就是单线程,以前也是,现在也是,未来也不会改变。这是这门语言的核心特征。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

同步任务和异步任务

单线程,就是指一次只能完成一件任务,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。但如果有一个任务的执行时间很长,比如文件的读取或者数据的请求等等,那么后面的任务就要一直等待,这就会影响用户的使用体验。

为了解决这种情况,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步任务(Synchronous)

又叫做非耗时任务,指的是在主线程上排队执行的那些任 只有前一个任务执行完毕,才能执行后一个任务;程序的执行顺序与任务的排列顺序是一致的、同步的

异步任务(Asynchronous)

又叫做耗时任务,异步任务由JavaScript 委托给宿主环境进行执行 当前一个异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数;后一个任务则是不等前一个任务的回调函数的执行而执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的

事件循环(Event Loop)

事件循环(Event Loop),又叫事件队列

js代码所在环境的编译器解析执行js代码的一种运行机制,即代码执行规则

事件循环不属于js代码本身的范畴,而是属于js编译器的范畴;js代码行为应当遵循所在环境的编译器的规则

(有小伙伴说:“这个js代码是个事件循环代码!”)

这种说法实际上是不合理的,再次声明:事件循环不属于js代码本身的范畴!

微任务与宏任务

1、微任务与宏任务就属于js代码的范畴

2、js代码主要分为两大类: 同步代码、异步代码

3、异步代码又分为:微任务与宏任务

同步任务:同步任务不需要进行等待,必须立即看到执行结果,比如console等

异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求等

异步任务,又可以细分为宏任务和微任务

以下是几种常见任务

任务(代码)宏/微 任务环境
事件宏任务浏览器
网络请求(Ajax)宏任务浏览器
setTimeout() 定时器宏任务浏览器 / Node
fs.readFile() 读取文件宏任务Node
Promise.then()微任务浏览器 / Node
async/await微任务浏览器 / Node

事件循环执行机制

1.进入到script标签,就进入到了第一次事件循环

2.遇到同步代码,立即执行

3.遇到宏任务,放入到宏任务队列里.

4.遇到微任务,放入到微任务队列里.

5.执行完所有同步代码

6.执行微任务代码

7.执行宏任务代码

8.微任务代码执行完毕,本次队列清空

9.寻找下一个宏任务,重复步骤1

以此反复直到清空所有宏任务,这种不断重复的执行机制,就叫做事件循环

image.png

你能说出以下代码的执行流程吗?

<!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>
    <script>
        console.log(1);
        async function fnOne() {
            console.log(2);
            await fnTwo(); // 右结合先执行右侧的代码, 然后等待
            console.log(3);
        }
        async function fnTwo() {
            console.log(4);
        }
        fnOne();

        setTimeout(() => {
            console.log(5);
        }, 2000);

        let p = new Promise((resolve, reject) => { // new Promise()里的函数体会马上执行所有代码
            console.log(6);
            resolve();
            console.log(7);
        })

        setTimeout(() => {
            console.log(8)
        }, 0)

        p.then(() => {
            console.log(9);
        })
        console.log(10);
    </script>
    <script>
        console.log(11);
        setTimeout(() => {
            console.log(12);
            let p = new Promise((resolve) => {
                resolve(13);
            })
            p.then(res => {
                console.log(res);
            })
            console.log(15);
        }, 0)
        console.log(14);
    </script>
</body>

</html>

部分内容参考于网络