JavaScript引擎的一个流行示例是Google的V8引擎。V8引擎被用在了Chrome和Nodejs里。javascript
浏览器中有不少几乎每一个开发都调用过的API,好比 setTimeout等,但引擎不提供这些API。java
JS是单线程的并发语言,这就意味着,在一个时间段内,它只能处理一项任务或执行一段代码。它有一个单一的调用栈(Single Call Stack),和堆(Heap),队列(Queue)组成的JS并发模型(Javascript Concurrentcy Model).
[可视化表示]promise
1. 调用栈(Call Stack) : 在程序中,它是一个记录程序调用的数据结构。每一个数据结构,也可称为栈帧。
来看一下MDN上的例子:浏览器
function foo(b) { var a = 5; return a * b + 10; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(6)); // 返回 100
当调用bar
时,建立了第一个帧 ,帧中包含了bar
的参数和局部变量。当bar
调用foo
时,第二个帧就被建立,并被压到第一个帧之上,帧中包含了foo
的参数和局部变量。当foo
返回时,最上层的帧就被弹出栈(剩下bar
函数的调用帧 )。当bar
返回的时候,栈就空了。
再加一张动态图:服务器
咱们有时会在浏览器的控制台看到长长的红色错误堆栈跟踪,它基本上指示了当前调用堆栈的状态,以及该函数在堆栈中从上到下失败的方式。网络
function foo(){ throw new Error("Oops!"); } function bar(){ foo(); } function baz() { bar(); } baz();
[Chrome浏览器]session
有时,咱们进入函数的无限循环,也会抛出错误。在Chrome中,栈里的最大深度为16,000。数据结构
2.堆(Heap):对象被分配在一个堆中,即用以表示一大块非结构化的内存区域。
3.队列(Queue):一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都关联着一个用以处理这个消息的函数。多线程
在事件循环期间的某个时刻,运行时从最早进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并做为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数老是会为其创造一个新的栈帧。并发
函数的处理会一直进行到执行栈再次为空为止;而后事件循环将会处理队列中的下一个消息(若是还有的话)。
基本上,当咱们评估 JS 代码的性能时,堆栈中的函数会使其速度慢或快,但执行d成千上万次迭代,或使用或执行超过数百万行代码的文件,速度将变慢,并且会保持堆栈占用或阻止。这样的代码或文件称为阻止脚本(Blocking script).
网络请求可能很慢,图片请求可能很慢,但幸运的是,服务器请求能够经过 AJAX(异步函数)来完成。咱们假设,这些网络请求是经过同步函数进行的,那么会发生什么?网络请求发送到某些服务器,不能就是另外一台在某处的计算机。如今,计算机发送回响应的速度可能会很慢。同时,若是我单击某个 CTA 按钮,或者须要执行一些其余渲染,则堆栈被阻止时不会执行任何操做。在多线程语言(如 Ruby)中,能够处理它,但在 Javascript 等单线程语言中,除非堆栈中的函数返回值,不然这是不可能的。网页将崩溃,由于浏览器不能作任何事情。若是咱们想要最终用户的流畅 UI,这不是理想的选择。咱们如何处理?
最简单的解决方案是使用异步回调,这意味着咱们运行代码的某些部分,并给它一个回调(函数),稍后将被执行。咱们都必定遇到异步回调,就像使用Node的任何AJAX请求同样,都是关于异步函数执行的。全部这些异步回调不会当即运行,将会在稍后运行,所以不能当即推送到堆栈内,不像同步函数,如它们到底去哪里,它们如何处理?$.get(),setTimeout(),setInterval(), Promises, etc.
`console.log(), mathematical operations.`
从上图中,网络请求在执行过程:
一、请求函数被执行,传递一个匿名函数做为回调,这个函数在未来某个时候,当响应(response)可用时执行。
二、“Script call done!” 被当即输出到控制台。
三、在未来某时刻,响应(response)从服务端返回,执行咱们的回调函数,并将其body输出到控制台。
调用方与响应的解耦使JavaScript运行时能够在等待异步操做完成并触发其回调前执行其余操做。在浏览器中,用于处理异步事件,是由C++来实现的,例如DOM事件,http请求,setTimeout等(知道了这一点以后,在Angular 2中,使用了区域,这些区域对这些API进行了猴子修补,以引发运行时更改检测,如今我能够了解它们如何实现此目的。)在浏览器中,当这些API被调用时,浏览器将建立进程处理异步的回调函数。
猴子补丁:在程序运行的过程当中动态的修改一些模块、类、方法,而不是在静态代码中去修改相应的实现。浏览器Web API-由浏览器建立的线程,使用C ++实现,用于处理异步事件,例如DOM事件,http请求,setTimeout等。
因为这些WebAPI自己不能将执行代码放到堆栈上,若是这样作了,它将随机出如今代码中间。因为这些WebAPI自己不能将执行代码放到堆栈上,若是这样作了,它将随机出如今代码中间。 上面讨论的消息回调队列展示了方法。 任何WebAPI在执行完,都会将回调(function)推送到此队列中。如今,事件循环(Event Loop)负责在队列(Queue)中执行这些回调,并在其为空时将其压入堆栈(Stack)。
进入队列,并不会当即被执行,只有当前Event Loop执行栈中的任务被执行完成后,才会被压入执行栈。
事件循环(Event Loop)的基本工做是同时查看堆栈(Stack)和任务队列(Queue),并在将堆栈视为空时将队列中的第一件事推入堆栈。 在处理任何其余消息以前,将彻底处理每一个消息或回调。
while (queue.waitForMessage()) { queue.processNextMessage(); }
在 Web 浏览器中,每当发生事件(Event)并附加事件侦听器(Listener)时,都将添加消息(Message)。若是没有侦听器(Listener),则事件(Event)将丢失。所以,单击具备 click 事件处理程序的元素(Element)将添加一条消息(Message) - 与任何其余事件同样。此回调函数(Callback function)的调用将用做调用堆栈中的初始帧,因为 JavaScript 是单线程的,在堆栈上返回全部调用以前,将中止进一步的消息轮询和处理。后续(同步)函数调用向堆栈添加新的调用帧。
如今能够看出,有不少不一样的任务队列,由上面可知,通常可分为两类,1)宏任务,2)微任务。
队列优先级
我先把结论COPY过来,有时间再写一篇文章详细说明。
小结
在JS引擎中,咱们能够按性质把任务分为两类,macrotask(宏任务)和 microtask(微任务)。浏览器JS引擎中:
macrotask(按优先级顺序排列): script(你的所有JS代码,“同步代码”), setTimeout, setInterval, setImmediate, I/O,UI rendering
microtask(按优先级顺序排列):process.nextTick,Promises(这里指浏览器原生实现的 Promise), Object.observe, MutationObserver
JS引擎首先从macrotask queue中取出第一个任务,执行完毕后,将microtask queue中的全部任务取出,按顺序所有执行;
而后再从macrotask queue(宏任务队列)中取下一个,执行完毕后,再次将microtask queue(微任务队列)中的所有取出;
循环往复,直到两个queue中的任务都取完。
因此,浏览器环境中,js执行任务的流程是这样的:第一个事件循环,先执行script中的全部同步代码(即 macrotask 中的第一项任务)
再取出 microtask 中的所有任务执行(先清空process.nextTick队列,再清空promise.then队列)
下一个事件循环,再回到 macrotask 取其中的下一项任务
再重复2
反复执行事件循环…NodeJS引擎中:
先执行script中的全部同步代码,过程当中把全部异步任务压进它们各自的队列(假设维护有process.nextTick队列、promise.then队列、setTimeout队列、setImmediate队列等4个队列)
按照优先级(process.nextTick > promise.then > setTimeout > setImmediate),选定一个 不为空 的任务队列,按先进先出的顺序,依次执行全部任务,执行过程当中新产生的异步任务继续压进各自的队列尾,直到被选定的任务队列清空。
重复2...
也就是说,NodeJS引擎中,每清空一个任务队列后,都会从新按照优先级来选择一个任务队列来清空,直到全部任务队列被清空。
资料来源:
https://developer.mozilla.org...
https://medium.com/@gaurav.pa...
https://blog.sessionstack.com...
https://developer.mozilla.org...
https://developer.mozilla.org...
https://blog.csdn.net/happyqy...