一、前言
本篇文章的目的是尽可能的解释清楚在js中的事件循环,继续通过三个问题着手:
- 什么是事件循环机制?
- javascript为什么需要事件循环?
- 事件循环的优缺点?
二、什么是事件循环?
- 简单的说,程序在执行代码的过程中,在主线程之外,维护一个事件队列。主线程在执行的过程中,循环的读取事件队列中的任务并执行。这一个整个流程就是事件循环机制。如图:
- 事件循环的核心,是通过事件触发的方式,将一个个任务排队放入事件队列,根据队列先进先出的特点。在不影响主线程执行的情况下,将任务按规则排列。既保证了任务的执行顺序,又不阻塞主线程的执行,这种运行机制,非常适合做单线程,多I/O,低运算量的javascript语言执行环境。
- v8只是个引擎,执行的环境是由浏览器提供的。
三、javascript为什么需要事件循环?
1. javascript是单线程语言
- javascript最初设计的目标,是作为浏览器的脚本语言,实现与用户的互动,所以它必须要被设计得足够轻量,简单。
- 而在浏览器中,与用户的互动,即操作网页的呈现方式。既然要改变网页的呈现,则必须改变网页的结构。网页的视图是基于dom树渲染,所以javascript必须要获得操作
dom
树的能力。 - 在操作
dom
树的过程中,有个限制,dom
树不能被一个以上的程序同时操作,因为可能会引发未知的错误。比如:// a线程执行 document.body.innerText = '我是a线程' // b线程执行 document.body.innerText = '我是b线程'
- 如上,假如两个线程同时操作
dom
,页面到底是呈现a线程的执行结果还是b线程的执行结果呢。这很容易引发错误,所以,javascript出现之初,就被设计成单线程,只有单线程才能避免以上问题(既要轻量,又要稳定)。 - 即使如今javascript引入了开启多线程的功能。但是开启的线程也被禁用了操作
dom
的api
。可能就是因为以上原因。
2. 因为单线程所以事件循环
- 一个线程在执行代码过程中,最影响它的执行效率的,就是阻塞的问题,。
- 而javascript被设计为网页脚本语言,假如javascript没有事件循环机制,而在与用户的交互过程中,需要应对一下情况:
- 调取网络请求,获取数据
- 用户点击鼠标或键盘,需展示交互操作
- 页面渲染需要执行的一些任务
- 内存回收浏览器任务
- 执行网络请求:一般网络请求需要一定的时延才能返回结果,而在这段时间内,单线程的javascript必须等待结果返回,才能继续下一步操作。这种情况就容易阻塞线程。对接下来的任务无发实时反馈。
- 用户交互:用户在任何时候,都有可能与浏览器进行交互操作。假如操作时,线程正在被阻塞,将会严重影响用户的交互体验。这是不可容忍的。
- 基于包括但不仅限于以上举例的原因,javascript是需要一个机制来帮忙处理这些不同情况,所以引入了事件循环机制。
- javascript引入事件循环机制。再遇到以上任务
- 执行网络请求:主线程遇到网络请求,将网络请求交给I/O线程(浏览器会为主线程维护I/O线程,处理各种事件),继续执行后续任务。等该网络请求完成,再通过I/O线程将结果包装成任务加入任务队列,等待主线程执行。
- 用户交互:用户点击页面,浏览器通知I/O线程,I/O线程也将事件详情包装成任务加入任务队列,等待主线程执行
- 基于以上原因,javascript引入了事件循环机制。
浏览器事件循环示意图
三、事件循环的优缺点
1. 优点
- 适合处理密集型I/O任务:通过事件循环机制,多个并行的任务,在JavaScript处理起来,只是将不同的任务分配给不同的线程,等待返回结果执行即可。所以处理速度超级快,有很高的实时性。
- 适合处理高并发:RESTful Api动辄发起成千上万条请求,但是请求本身并没有太多的计算量,开启多线程处理等待结果又太浪费机器性能。所以事件循环非常适合处理此种场景。
- 适合处理少量业务逻辑:例如浏览器中,在处理用户交互事件,页面渲染等少量业务逻辑的场景上,具有很好实时性,能给用户提供很流畅的体验。
2. 缺点
- 不适合cpu密集型应用:因为JavaScript单线程的设计,因此,对于高强度运算的任务,可能会因为运算能力有限,导致任务处理时间过长,影响后续任务执行。
解决办法: 将单个cpu密集型任务拆成多个子任务,留出一定时间间隔执行其他任务。
- cpu利用率低:因为单线程的原因,cpu多核性能利用率低。
解决办法: 1.开启宿主提供的多线程功能。 2. 如果在node端,通过nginx代理,同时开启多个node进程进行处理。
- 安全性低:因为单线程的原因,如果主线程发生错误,将直接导致应用崩溃。