Rain120 / Web-Study

日常学习,工作写的笔记
66 stars 108 forks source link

JavaScript事件循环(Event Loop)? #8

Open Rain120 opened 5 years ago

Rain120 commented 5 years ago

众所周知,Javascript是单线程的脚本语言,也就是说,同一个时间只能做一件事。它的主要用途是与用户互动,以及操作DOM。由于它的用途决定了它只能是单线程的。

举个例子,假定JavaScript同时有两个线程a, b,这两个线程都对一个DOM进行不同的操作,a线程添加内容,b线程删除内容,那浏览器怎么判断呢?即便后来HTML5提出Web Worker来允许Javascript创建多个线程,但本质上还是主线程控制子线程,只是分担主线程的一部分计算任务,并没有改变JavaScript单线程的本质。

同步和异步

同步和异步

同步:在执行某个函数后,能够在执行后直接得到返回结果的操作。

异步:在执行某个函数后,不能够在执行后直接得到返回结果的,而需要通过其他手段才能得到结果的操作。

同步就是串行,异步就是并行。

JavaScript变量类型

栈内存(stack)与堆内存(heap)

image

image

JavaScript并发模型基于“事件循环”, 浏览器提供运行时环境来执行我们的JavaScript代码, 浏览器主要包括 调用堆栈 ,事件循环 ,任务队列 和 Web API 。像setTimeout,setInterval和Promise这样的全局函数不是JavaScript的一部分,而是Web API的一部分。JavaScript环境的可视化表示如下所示:

image

JS调用堆栈是后进先出(LIFO)。引擎一次从堆栈中获取一个函数,并从上到下依次运行代码。每次遇到一些异步代码(如setTimeout)时,它都会将其交给Web API(箭头1)。因此,每当触发事件时,callback都会被发送到任务队列(箭头2)。Event Loop不断监视任务队列,并按照排队顺序一次处理一个callback。每当调用堆栈为空时,循环检索回调并将其放入堆栈(箭头3)进行处理。请记住,如果调用堆栈不为空,则事件循环不会将任何callbacks推送到堆栈。

任务队列(task queue)

事件循环具有一个或多个任务队列。 任务队列是一个有序的任务列表,一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。

任务队列是先进先出。

执行栈

JavaScript在主线程执行任务会形成一个执行栈。

JavaScript在执行完执行栈的内容后,会读取任务队列中的任务加入执行栈并依次执行。

宏任务和微任务

在JavaScript中异步任务中分为两种任务,宏任务(macro)task和微任务(microtask),那么我们是如何区分的这两种任务的呢?

宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行返回到event loop(包括任何额外排队的项)之前清空。

宏任务(macrotask):

script(全局任务)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

微任务(microtask):

Promise、 MutaionObserver、Object.observer,process.nextTick(Node.js环境)

它又是如何执行的呢?

image

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理的步骤如下: 1、执行栈选择宏任务(script)执行(有就执行,没有就下一步); 2、检查是否存在 Microtasks,如果存在则不停地执行,直至清空 Microtasks Queue; 3、render(每一次事件循环,浏览器都可能会去更新渲染); 4、重复上述步骤。

你还可以通过这个非常棒的可视化工具来理解调用堆栈

参考资料

Tasks, microtasks, queues and schedules

So you think you know JavaScript?

JavaScript 运行机制详解:再谈Event Loop

tasks-microtasks-queues-and-schedules

event-loops

ask-queues

Eventloop不可怕,可怕的是遇上Promise