Event Loop

本文主要参阅了如下两篇文章,对JS的Event Loop运行机制基础知识进行了整理。
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
JavaScript 运行机制详解:再谈Event Loophtml

背景知识

进程与线程

你们都知道JavaScript是单线程的,这就引伸出一个问题,进程与线程是什么,他们的区别是什么?
先给出进程和线程的定义:前端

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是创建在进程的基础上的一次程序运行单位,一个进程中能够有多个线程)

用工厂和工人的例子来形象阐述:ajax

- 进程是一个工厂,工厂有它的独立资源 -> 系统分配的内存(独立的一块内存)

- 工厂之间相互独立 -> 进程之间相互独立

- 线程是工厂中的工人,多个工人协做完成任务 -> 多个线程在进程中协做完成任务

- 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

- 工人之间共享工厂的资源 -> 同一进程下的各个线程之间共享进程的内存空间(包括代码段、数据集、堆等)

补充:segmentfault

  • 咱们所说的单线程和多线程,是指一个进程内是单一线程仍是多线程。
  • 进程间的通讯方式包括: 管道pipe、 命名管道FIFO、消息队列MessageQueue、共享存储SharedMemory、信号量Semaphore、套接字Socket、信号。

浏览器是多进程的

关于浏览器进程问题能够简单基础三点:浏览器

  • 浏览器是多进程的。
  • 浏览器之因此可以运行,是由于系统给它的进程分配了资源(cpu、内存)。
  • 简单点理解,每打开一个Tab页,就至关于建立了一个独立的浏览器进程。

平时 coding 接触到最多的一个浏览器进程是浏览器渲染进程(浏览器内核),它管理着页面渲染。脚本执行,事件处理等。要同时处理这么多事情,渲染进程显然是多线程的,它主要包括如下5个常驻线程:多线程

  1. GUI渲染线程,负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
  2. JS引擎线程,也称为JS内核,负责处理Javascript脚本程序,(例如V8引擎)。
  3. 事件触发线程,用来控制事件循环(能够理解为,JS引擎线程本身都忙不过来,须要浏览器另开线程协助)。
  4. 定时触发器线程,浏览器定时计数器并非由JavaScript引擎计数的,(由于JavaScript引擎是单线程的, 若是处于阻塞线程状态就会影响记计时的准确),JS中经常使用的setIntervalsetTimeout就归这个线程管理。
  5. 异步http请求线程,也就是ajax发出http请求后,接收响应、检测状态变动等都是这个线程管理的。

咱们常说的JavaScript是单线程的,其实就是说的JS引擎是单线程的,它仅仅是浏览器渲染进程种的一个线程。为何呢?由于JavaScript的主要做用是与用户互动,以及操做DOM,若是JavaScript有两个线程,一个线程对一个DOM节点执行 A 操做,另外一个线程这个DOM节点执行 B 操做,那么就会起冲突,因此JavaScript在前端的应用就注定了它是单线程的。异步

然而JavaScript的单线程特性就注定咱们不用它去完成密集的 cpu 运算,由于密集 cpu 运算耗时过长,阻塞页面渲染。为了解决这个问题,HTML5提出 Web Worker 标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM函数

Event Loop

JavaScript 是单线程的带来的问题是:全部任务都必须同步执行,问题就出现了,不少 I/O 过程是很是耗时的(如http 请求数据),若是要等到 I/O 过程结束再执行后续任务,就会出现页面的卡顿、cpu 的闲置。因而异步的任务就出现了,异步任务是指挂起处于等待中的任务,继续执行同步任务,等到结果返回再去继续执行被挂起的任务。因而,JavaScript 的任务能够分为同步任务和异步任务。下面就引出 Event Loop 机制:oop

  • 全部同步任务都在主线程上执行,造成一个执行栈
  • 主线程以外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的全部同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到执行栈中,开始执行。

clipboard.png

如上图所示,执行栈中的代码会调用一个异步的API,它们会在任务队列中添加各类事件(或者说回调函数),另外用户的操做如clickmousedown等都会在任务队列中添加事件。只要执行栈中的代码执行完毕,主线程就会去读取任务队列,将可执行的回调函数放到执行栈中执行。布局

总结一下:

执行栈执行完毕 -> 主线程读取任务队列,并执行回调函数 -> 执行栈执行完毕 -> 主线程读取任务队列,并执行回调函数 ...

这个过程一直循环下去,因此就叫事件循环(Event Loop)。

setTimeout 和 setInterval

前面提到了浏览器的定时触发器线程,它的主要做用就是计时,setTimeoutsetInterval 就由它来控制,原理就是到达设置时间后,往任务队列中添加这两个函数中指定的回调函数。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。可是须要注意的是,实际是计时结束后定时触发器线程才会将回调函数放到任务队列中去,此时任务队列中这个回调以前可能已经有一些事件待处理,而且必定要执行栈的任务执行完后才会开始执行任务队列中的任务,因此 setTimeout() 中回调开始执行的时间是:执行栈执行时间 + 任务队列前方回调执行时间 + 延迟时间

setInterval() 方法可按照指定的时间间隔来周期性调用函数或计算表达式。它的问题在于:每次都精确的隔一段时间将一个回调放到任务队列中,并无考虑到内部回调函数执行所需时间,这就会致使两种问题:

  • 回调函数执行须要时间,两个函数执行的时间间隔会小于设定值;
  • 若是回调函数执行时间大于设定间隔,就会出现上一个加入任务队列中的回调还没执行完,下一个回调就被加入任务队列了,就会出现累计效应,即后面的回调会连续执行。
相关文章
相关标签/搜索