您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

JS中的事件循环和任务队列

时间:2020-03-08 17:30:24  来源:  作者:

JS引擎(The JAVAScript Engine)

JavaScript引擎的一个流行示例是google的V8引擎。V8引擎被用在了Chrome和Nodejs里。

JS中的事件循环和任务队列

 

运行时(The Runtime)

浏览器中有很多几乎每个开发都调用过的API,比如 setTimeout等,但引擎不提供这些API。

JS中的事件循环和任务队列

 

JS是单线程的并发语言,这就意味着,在一个时间段内,它只能处理一项任务或执行一段代码。它有一个单一的调用栈(Single Call Stack),和堆(Heap),队列(Queue)组成的JS并发模型(Javascript Concurrentcy Model).

JS中的事件循环和任务队列

 

[可视化表示]

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 返回的时候,栈就空了。

再加一张动态图:

JS中的事件循环和任务队列

 

我们有时会在浏览器的控制台看到长长的红色错误堆栈跟踪,它基本上指示了当前调用堆栈的状态,以及该函数在堆栈中从上到下失败的方式。

function foo(){
throw new Error("Oops!");
}
function bar(){
    foo();
}
function baz() {
    bar();
}
baz();
JS中的事件循环和任务队列

 

[Chrome浏览器]

有时,我们进入函数的无限循环,也会抛出错误。在Chrome中,栈里的最大深度为16,000。

JS中的事件循环和任务队列

 

2.堆(Heap):对象被分配在一个堆中,即用以表示一大块非结构化的内存区域。

3.队列(Queue):一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都关联着一个用以处理这个消息的函数。

在事件循环期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。

函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。

Event Loop

基本上,当我们评估 JS 代码的性能时,堆栈中的函数会使其速度慢或快,但执行d成千上万次迭代,或使用或执行超过数百万行代码的文件,速度将变慢,而且会保持堆栈占用或阻止。这样的代码或文件称为阻止脚本(Blocking script).

网络请求可能很慢,图片请求可能很慢,但幸运的是,服务器请求可以通过 AJAX(异步函数)来完成。我们假设,这些网络请求是通过同步函数进行的,那么会发生什么?网络请求发送到某些服务器,不能就是另一台在某处的计算机。现在,计算机发送回响应的速度可能会很慢。同时,如果我单击某个 CTA 按钮,或者需要执行一些其他渲染,则堆栈被阻止时不会执行任何操作。在多线程语言(如 Ruby)中,可以处理它,但在 Javascript 等单线程语言中,除非堆栈中的函数返回值,否则这是不可能的。网页将崩溃,因为浏览器不能做任何事情。如果我们想要最终用户的流畅 UI,这不是理想的选择。我们如何处理?

最简单的解决方案是使用异步回调,这意味着我们运行代码的某些部分,并给它一个回调(函数),稍后将被执行。我们都一定遇到异步回调,就像使用Node的任何AJAX请求一样,都是关于异步函数执行的。所有这些异步回调不会立即运行,将会在稍后运行,因此不能立即推送到堆栈内,不像同步函数,如它们到底去哪里,它们如何处理? $.get(),setTimeout(),setInterval(), Promises, etc. `console.log(), mathematical operations.`

JS中的事件循环和任务队列

 

从上图中,网络请求在执行过程:

1、请求函数被执行,传递一个匿名函数作为回调,这个函数在将来某个时候,当响应(response)可用时执行。

2、“Script call done!” 被立即输出到控制台。

3、在将来某时刻,响应(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();  
}
JS中的事件循环和任务队列

 

在 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引擎中,每清空一个任务队列后,都会重新按照优先级来选择一个任务队列来清空,直到所有任务队列被清空。



Tags:JS   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言几乎所有.NET序列化程序的实现基础都是反射。下列代码是Newtonsoft.Json的实现:protectedvirtualJsonPropertyCreateProperty(MemberInfomember,MemberSerializationmemb...【详细内容】
2021-12-28  Tags: JS  点击:(2)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  Tags: JS  点击:(13)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  Tags: JS  点击:(23)  评论:(0)  加入收藏
前言前几天有粉丝在群里问了一个json文件处理的问题。看上去他只需要follower和ddate这两个字段下的对应的值。我们知道json是一种常见的数据传输形式,所以对于爬取数据的数...【详细内容】
2021-12-07  Tags: JS  点击:(40)  评论:(0)  加入收藏
作者:前端进阶者来源:前端进阶学习交流一、前言 我们经常在网页上 ,游戏界面加载时会看到加载进度条的效果,我们往往会以为这些加载进度条的效果,很难实现。今天教大家JS+CSS结合...【详细内容】
2021-11-05  Tags: JS  点击:(45)  评论:(0)  加入收藏
今天我们将尝试下花 1 分钟的时间简单地了解下什么是 JS 代理对象(proxies)?我们可以这样理解,JS 代理就相当于在对象的外层加了一层拦截,在拦截方法里我们可以自定义一些个性化...【详细内容】
2021-10-18  Tags: JS  点击:(51)  评论:(0)  加入收藏
带有多个条件的 if 语句把多个值放在一个数组中,然后调用数组的 includes 方法。// bad if (x === "abc" || x === "def" || x === "ghi" || x === "jkl") { //logic } // be...【详细内容】
2021-09-27  Tags: JS  点击:(58)  评论:(0)  加入收藏
利用JS的CryptoJS 3.x和PHP的openssl_encrypt,openssl_decrypt实现AES对称加密解密,由于需要两种语言对同一字符串的操作,而CryptoJS 的默认加密方式为“aes-256-cbc”,PHP端也...【详细内容】
2021-09-16  Tags: JS  点击:(79)  评论:(0)  加入收藏
作者:JShaman.com:w2sft内容预告:本文将实例讲解以下JS代码混淆加密技术:方法名转义和转码、成员表达式转IIFE、函数标准化、数值混淆、布尔型常量值混淆、二进制表达式转为调用...【详细内容】
2021-09-03  Tags: JS  点击:(82)  评论:(0)  加入收藏
Web 浏览器日益强大,网站和 Web 应用程序的复杂性也在增加。几十年前需要超级计算机的操作现在可以在智能手机上运行,其中之一就是人脸检测。检测和分析人脸的能力非常有用,因...【详细内容】
2021-08-20  Tags: JS  点击:(105)  评论:(0)  加入收藏
▌简易百科推荐
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === 'a') { b = true} else { b = false}// goodb = a === 'a'2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Mason程    Tags:JavaScript   点击:(6)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  未来讲IT    Tags:JavaScript   点击:(20)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  前端明明    Tags:js   点击:(23)  评论:(0)  加入收藏
作者:一川来源:前端万有引力 1 写在前面Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的...【详细内容】
2021-12-06  Nodejs开发    Tags:Javascript   点击:(19)  评论:(0)  加入收藏
概述DOM全称Document Object Model,即文档对象模型。是HTML和XML文档的编程接口,DOM将文档(HTML或XML)描绘成一个多节点构成的结构。使用JavaScript可以改变文档的结构、样式和...【详细内容】
2021-11-16  海人为记    Tags:DOM模型   点击:(35)  评论:(0)  加入收藏
入口函数 /*js加载完成事件*/ window.onload=function(){ console.log("页面和资源完全加载完毕"); } /*jQuery的ready函数*/ $(document).ready(function(){ co...【详细内容】
2021-11-12  codercyh的开发日记    Tags:jQuery   点击:(36)  评论:(0)  加入收藏
一、判断是否IE浏览器(支持判断IE11与edge)function IEVersion() {var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isIE = userAgent.indexOf("comp...【详细内容】
2021-11-02  V面包V    Tags:Javascript   点击:(40)  评论:(0)  加入收藏
Null、Undefined、空检查普通写法: if (username1 !== null || username1 !== undefined || username1 !== '') { let username = username1; }优化后...【详细内容】
2021-10-28  前端掘金    Tags:JavaScript   点击:(51)  评论:(0)  加入收藏
今天我们将尝试下花 1 分钟的时间简单地了解下什么是 JS 代理对象(proxies)?我们可以这样理解,JS 代理就相当于在对象的外层加了一层拦截,在拦截方法里我们可以自定义一些个性化...【详细内容】
2021-10-18  前端达人    Tags:JS   点击:(51)  评论:(0)  加入收藏
带有多个条件的 if 语句把多个值放在一个数组中,然后调用数组的 includes 方法。// bad if (x === "abc" || x === "def" || x === "ghi" || x === "jkl") { //logic } // be...【详细内容】
2021-09-27  羲和时代    Tags:JS   点击:(58)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条