十万个Web前端面试题之EventLoop

143 阅读5分钟

前言

最近偶尔也有参与人员面试,为了防止每次面试都要花时间来重新准备,这边写个专题,名字就随便点,叫十万个面试题系列吧。

虽然我一直标榜自己是全栈工程师,但由于项目的需求,近几年精力主要还是集中在Web前端以及软件设计上,所以就先写Web前端的相关知识。

EventLoop

第一篇文章,我直接选了EventLoop,因为我觉得这是Web前端一个非常重要且特色的功能。做Web前端的,或多或少都对它有一定的了解。算是入门必备知识了。而且Ry也是因为JavaScript的这个特性,才弄了个Node.js出来。

概念

由于JavsScript是单线程的,至于为什么是单线程,嗯,历史遗留问题,我们这边就不展开了。单线程就是说,它一次就只能做一件事情,你让它送快递,好,它走了,你再想让它给你做其它事情,不好意思,等他送完快递回来再说。

一般情况下,这不会有啥问题,毕竟程序速度快嘛,但指不定发生啥问题是吧,比如说等红绿灯呀,或者客户家住一百楼还没电梯的,你别说,I/O操作它就住得高,和这家伙沟通一次,你还以为死机了。 JavaScript也知道这样不行,于是就设置了两个线程,一个线程还是处理主程序,一个线程就出来专门负责主程序和其它进程的通信,这个分出来的线程,就叫EventLoop。

主程序需要调用I/O操作,就会和EventLoop说:“嘿,老弟,帮我取下数据”,然后EventLoop就屁颠屁颠的去取数据,而主程序呢?当然不等了,直接做其它事情了,EventLoop取完数据后,就跑到主程序那边,把数据给主程序。完美的解决了I/O阻塞的问题。这套办法被称为异步非阻塞I/O请求,在并发请求上非常有先天优势,非常适合处理高并发请求,所以同等资源条件下,Node.js比Java的并发量是要高的。当然,缺点主是慢,因为要等EventLoop通知。

浏览器与Node.js

虽说EventLoop是JavaScript里面的一种定义,但实现它,浏览器和Node.js是不同的方案,浏览器实现的是基于HTML标准定义,下面是HTML标准定义对于EventLoop的解释。

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.

The event loop of a similar-origin window agent is known as a window event loop. The event loop of a dedicated worker agent, shared worker agent, or service worker agent is known as a worker event loop. And the event loop of a worklet agent is known as a worklet event loop.

而Node.js是基于libuv来实现EventLoop的,下面是Node.js文档里面的简要说明

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

宏任务与微任务

那哪些代码会触发EventLoop呢?嗯,所有的异步函数都可以触发EventLoop。那EventLoop有没有先后顺序优先级之类的呢?这就引出了宏任务与微任务。

大家都知道,前端setTimeout或Promise都是异步函数,但他们有什么区别呢?

我们先说下浏览器中的EventLoop,它其实是由主线程(main thread)、执行栈(call-stack)和队列三个东西组成,执行栈的代码,是提供给主线程调用的,而列队里面的代码,是等待放入执行栈的。EventLoop就是这样一个,不断的把任务队列中的代码放到执行栈,执行栈的代码被主线程执行的过程。

浏览器中的JavaScript代码是由无数个函数组成,为了简单讲,我们认为有一个统一的入口函数,这个入口函数就被放到执行栈中,然后被主线程执行后,入口函数中发现很多子函数,又把这些子函数放到队列中,执行栈如果为空的话,就把队列的代码压入执行栈,如果非空的话,就进行执行执行栈的代码。

那任务执行过程中有先后顺序吗?当然有顺序,任务其实分为宏任务和微任务,他们的执行顺序就不一定。

  • 宏任务:script代码块,setTimeout、setInterval、I/O、UI Rendering

  • 微任务:Promise等

他们的执行顺序就是先执行宏任务,在执行过程中,发现其它宏任务,则把这个任务放到宏任务队列中,继续执行当前任务,如果碰到微任务,则把其放到微任务队列中,还是继续当前任务,当前任务执行完毕,把微任务列表中内容拿出来执行,微任务执行完成,再把宏任务拿出来执行,嗯,就是这么回事,如果你听的还是云里雾里,那我这边在网上找了一张图,就比较清晰明了了。

特殊

当然,由于Node.js的EventLoop实现机制和浏览器不同,导致在任务执行顺序上可能存在差异,这个需要注意。

实例

这边有一个面试经典例子,大家有兴趣的,回复下答案吧

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')