浅谈js的事件循环(Event Loop)

1,577 阅读4分钟
  • 事件循环是js这门语言的一大特点。
  • 了解事件循环机制,有助于日常开发中遇到的一些异步问题。
  • 而且还是前端面试一经常考点。
  • 故本人结合一些文章和个人的一些开发经验,浅淡一下

一,js是一门单线程语言

  1. js的单线程
a. js是一门单线程的语言。这意味着它在同一时间,只能做同一件事。
b. 但为了协调事件,用户交互,UI渲染和网络行为交互等。
c. 防止主线程被阻塞,Event Loop便应运而生。
如: 发送一个网络请求,需要等待一定时间,这个时间内主线程空闲出来做些其他事;
  1. 为什么js是单线程?
a. js主要是运行在浏览器的脚步语言,主要是操作dom;
b. 举个例子,如果js同时有多个线程。多个线程同时操作同一个dom,
   这时浏览器该依据那个线程,如何判断优先级
c. 为了避免上述问题,并降低复杂度,故js被设计成单线程语言。

二,概念的理解

  1. 同步任务
同步任务指的是,在主线程上排队执行的任务,
只有前一个任务执行完毕,才能执行后一个任务;
  1. 异步任务
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,
只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  1. 异步执行机制
a. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack);
b. 主线程之外,还存在一个"任务队列"(task queue)。
   只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
c. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",
   看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,
   进入执行栈,开始执行。
d. 主线程不断重复上面的第三步。
  1. 任务队列
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。
  1. 事件循环
主线程从"任务队列"中读取事件,这个过程是循环不断的,
所以整个的这种运行机制又称为Event Loop(事件循环)。
  1. 宏任务与微任务
异步任务分为 宏任务(macrotask) 与 微任务 (microtask),
不同的API注册的任务会依次进入自身对应的队列中,
然后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务:script(整体代码)、setTimeout、setInterval、UI 渲染、 
        I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
        
微任务:Promise、 MutaionObserver、process.nextTick(Node.js环境)
  1. Event Loop(事件循环)
1)执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行;
(2)检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列;
(3)更新render(每一次事件循环,浏览器都可能会去更新渲染);
(4)重复以上步骤;
  1. 宏任务 > 所有微任务(核心),上代码
<script>// 宏任务1
    console.log('宏任务1'); // 宏任务1中的同步任务
    
    setTimeout(() => {// 宏任务1中的另一个宏任务3
        console.log('宏任务1中的另一个宏任务3');
        
        new Promise((resolve, reject) => {
            resolve('宏任务3中的微任务2');
        }).then(data => {// 宏任务3中的微任务2
            console.log(data)
        })
        
    }, 300);
    
    new Promise((resolve, reject) => {
        resolve('宏任务1中的微任务1');
    }).then(data => {// 宏任务1中的微任务1
        console.log(data);
        
        setTimeout(() => {// 微任务1中的另一个宏任务4
            console.log('微任务1中的另一个宏任务4');
        }, 300);
        
    });
    
</script>// 宏任务1

<script>// 宏任务2
    console.log('宏任务2')
</script>// 宏任务2
  • 上述代码的执行结果

  • 代码结果分析:
1. 宏任务1=>宏任务1中的微任务1
   表明执行完宏任务就执行微任务(忽略宏任务2,便于理解)
2. 然后到 宏任务1中宏任务3=>宏任务3中的微任务2
   再次表明执行完本宏任务后就执行本宏任务下的微任务
3. 最后到微任务1中的宏任务4