作者:Sukhjinder Arora
译者:前端小智
来源:medium

为了保证的可读性,本文采用意译而非直译。

想优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

JS 是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说JS引擎一次只能在一个线程里处理一条语句。

虽然单线程简化了编程代码,因为这样咱们不必太担心并发引出的问题,这也意味着在阻塞主线程的情况下执行长时间的操作,如网络请求。

想象一下从API请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。这就是引入异步 JS 的原因。使用异步 (如 回调函数、promiseasync/await),可以不用阻塞主线程的情况下长时间执行网络请求。

了解异步的工作方式之前,咱们先来看看同步是怎么样工作的。

同步 JS 是如何工作的?

在深入研究异步JS之前,先来了解同步 JS 代码在 JavaScript 引擎中执行情况。例如:

const second = () => {console.log('Hello there!');}const first = () => {console.log('Hi there!');second();console.log('The End');}first();

要理解上述代码如何在 JS 引擎中执行,咱们必须理解什么是执行上下文调用栈(也称为执行堆栈)。

函数代码在函数执行上下文中执行,全局代码在全局执行上下文中执行。每个函数都有自己的执行上下文。

调用栈

调用堆栈顾名思义是一个具有LIFO(后进先出)结构的堆栈,用于存储在代码执行期间创建的所有执行上下文。

JS 只有一个调用栈,因为它是一种单线程编程语言。调用堆栈具有 LIFO 结构,这意味着项目只能从堆栈顶部添加或删除。

回到上面的代码,尝试理解代该码是如何在JS引擎中执行。

const second = () => {console.log('Hello there!');
}
const first = () => {console.log('Hi there!');second();console.log('The End');
}
first();

这里发生了什么?

当执行此代码时,将创建一个全局执行上下文(由main()表示)并将其推到调用堆栈的顶部。当遇到对first()的调用时,它会被推送到堆栈的顶部。

接下来,console.log('Hi there!')被推送到堆栈的顶部,当它完成时,它会从堆栈中弹出。之后,我们调用second(),因此second()函数被推到堆栈的顶部。

console.log('Hello there!')被推送到堆栈顶部,并在完成时弹出堆栈。second() 函数结束,因此它从堆栈中弹出。

console.log(“the End”)被推到堆栈的顶部,并在完成时删除。之后,first()函数完成,因此从堆栈中删除它。

程序在这一点上完成了它的执行,所以全局执行上下文(main())从堆栈中弹出。

异步 JS 是如何工作的?

现在咱们已经对调用堆栈和同步JAS的工作原理有了基本的了解,回到异步JS上。

阻塞是什么?

假设咱们正在以同步的方式进行图像处理或网络请求。例如:

const processImage = (image) => {/*** doing some operations on image**/console.log('Image processed');
}
const networkRequest = (url) => {/*** requesting network resource**/return someData;
}
const greeting = () => {console.log('Hello World');
}
processImage(logo.jpg);
networkRequest('www.somerandomurl.com');
greeting();

做图像处理和网络请求需要时间,当processImage()函数被调用时,它会根据图像的大小花费一些时间。

processImage() 函数完成后,将从堆栈中删除它。然后调用 networkRequest() 函数并将其推入堆栈。同样,它也需要一些时间来完成执行。

最后,当networkRequest()函数完成时,调用greeting()函数。

因此,咱们必须等待函数如processImage()networkRequest()完成。这意味着这些函数阻塞了调用堆栈或主线程。因此,在执行上述代码时,咱们不能执行任何其他操作,这是不理想的。

解决办法是什么?

最简单的解决方案是异步回调,各位使用异步回调使代码非阻塞。例如:

const networkRequest = () => {setTimeout(() => {console.log('Async Code');}, 2000);
};
console.log('Hello World');
networkRequest();

这里使用了setTimeout方法来模拟网络请求。请记住setTimeout不是JS引擎的一部分,它是Web Api的一部分。

为了理解这段代码是如何执行的,咱们必须理解更多的概念,比如事件轮询和回调队列(或消息队列)。

事件轮询、web api和消息队列不是JavaScript引擎的一部分,而是浏览器的JavaScript运行时环境或Nodejs JavaScript运行时环境的一部分(对于Nodejs)。在Nodejs中,web api被c/c++ api所替代。

现在让我们回到上面的代码,看看它是如何异步执行的。

const networkRequest = () => {setTimeout(() => {console.log('Async Code');}, 2000);
};console.log('Hello World');networkRequest();console.log('The End');

当上述代码在浏览器中加载时,console.log(' Hello World ') 被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对 networkRequest() 的调用,因此将它推到堆栈的顶部。

下一个 setTimeout() 函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数: 1) 回调和 2) 以毫秒(ms)为单位的时间。

setTimeout() 方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。cosole.log(“the end”) 被推送到堆栈中,在完成后执行并从堆栈中删除。

同时,计时器已经过期,现在回调被推送到消息队列。但是回调不会立即执行,这就是事件轮询开始的地方。

事件轮询

事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。如果调用堆栈是空的,它将检查消息队列,看看是否有任何挂起的回调等待执行。

在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部。

然后是 console.log(“Async Code”) 被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。

消息队列还包含来自DOM事件(如单击事件和键盘事件)的回调。例如:

document.querySelector('.btn').addEventListener('click',(event) => {console.log('Button Clicked');
});

对于DOM事件,事件侦听器位于web api环境中,等待某个事件(在本例中单击event)发生,当该事件发生时,回调函数被放置在等待执行的消息队列中。

同样,事件轮询检查调用堆栈是否为空,并在调用堆栈为空并执行回调时将事件回调推送到堆栈。

延迟函数执行

咱们还可以使用setTimeout来延迟函数的执行,直到堆栈清空为止。例如

const bar = () => {console.log('bar');
}
const baz = () => {console.log('baz');
}
const foo = () => {console.log('foo');setTimeout(bar, 0);baz();
}
foo();

打印结果:

foo
baz
bar

当这段代码运行时,第一个函数foo()被调用,在foo内部我们调用console.log('foo'),然后setTimeout()被调用,bar()作为回调函数和时0秒计时器。

现在,如果咱们没有使用 setTimeout,bar() 函数将立即执行,但是使用 setTimeout0秒计时器,将bar的执行延迟到堆栈为空的时候。

0秒后,bar()回调被放入等待执行的消息队列中,但是它只会在堆栈完全空的时候执行,也就是在bazfoo函数完成之后。

ES6 任务队列

我们已经了解了异步回调和DOM事件是如何执行的,它们使用消息队列存储等待执行所有回调。

ES6引入了任务队列的概念,任务队列是 JS 中的 promise 所使用的。消息队列和任务队列的区别在于,任务队列的优先级高于消息队列,这意味着任务队列中的promise 作业将在消息队列中的回调之前执行,例如:

const bar = () => {console.log('bar');
};const baz = () => {console.log('baz');
};const foo = () => {console.log('foo');setTimeout(bar, 0);new Promise((resolve, reject) => {resolve('Promise resolved');}).then(res => console.log(res)).catch(err => console.log(err));baz();
};foo();

打印结果:

foo
baz
Promised resolved
bar

咱们可以看到 promisesetTimeout 之前执行,因为 promise 响应存储在任务队列中,任务队列的优先级高于消息队列。

小结

因此,咱们了解了异步 JS 是如何工作的,以及调用堆栈、事件循环、消息队列和任务队列等概念,这些概念共同构成了 JS 运行时环境。虽然成为一名出色的JS开发人员并不需要学习所有这些概念,但是了解这些概念是有帮助的。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

原文:https://blog.bitsrc.io/understanding-asynchronous-javascript-the-event-loop-74cd408419ff

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

js 延迟几秒执行_息息相关的 JS 同步,异步和事件轮询相关推荐

  1. js 延迟几秒执行_深入研究 Node.js 的回调队列

    队列是 Node.js 中用于有效处理异步操作的一项重要技术. 在本文中,我们将深入研究 Node.js 中的队列:它们是什么,它们如何工作(通过事件循环)以及它们的类型. Node.js 中的队列是 ...

  2. js 延迟几秒执行ifarme_Node.js调试之llnode篇

    概述:本文简单介绍一下如何使用llnode来分析Node应用挂掉之后产生的Core dump文件,帮助您快速定位问题. Core dump 首先介绍一下什么是Core dump,根据维基百科,它是应用 ...

  3. js 延迟几秒执行ifarme_js设定延迟时间的函数

    1.如果想要在执行一个js函数之前延迟一段时间应该怎么做? 答:"setTimeout('update()',1000);" 其中update()函数就是延迟后执行的函数,后面的时 ...

  4. js延迟2秒执行事件

    有时候,我们在做修改回显数据时,就需要默认触发一些事件,但是由于数据没有很快从服务器中取回,所以就有延迟执行js事件 setTimeout(function () {// 这里就是处理的事件 }, 2 ...

  5. js 延迟几秒执行ifarme_延时加载JavaScript代码提高速度_javascript技巧 -

    ...值: none 0 e1. 利用animation属性实现循环间的延时执行实例教程简介:先来介绍一下animation定义和用法,animation 属性是一个简写属性,用于设置六个动画属性:2 ...

  6. js 延迟几秒执行ifarme_用JS控制iframe里的页面,做到3秒自动换一个。

    展开全部 假定iframe的id为ifr 下面是js程序 var times = 10;  //循环次数32313133353236313431303231363533e4b893e5b19e3133 ...

  7. android 延迟2秒执行_每天30秒让你更懂汽车(10自动变速器2执行装置)

    本篇内容 848字,阅读时长约 55 秒 执行装置包括换挡离合器.换挡制动器和单向离合器等. 控制装置根据汽车不同行驶条件,分别在执行装置中建立或卸除油压, 从而得到自动变速器的不同挡位. 1.换挡离 ...

  8. 超简单JS延迟5秒加载方法代码

    JS延迟5秒加载方法 setTimeout( function(){ //add your code }, 5 * 1000 ); //延迟5000毫米 5000等于5秒哦

  9. 轮询机制php,JS事件轮询机制讲解

    JS是单线程语言,深入理解JS里的Event Loop,本文主要和大家分享JS事件轮询机制,希望能帮助到大家. JS的执行机制(一): 1.首先判断JS是同步还是异步,同步就进入主进程,异步就进入ev ...

  10. Node.js的事件轮询Event Loop原理解释

    事件轮询主要是针对事件队列进行轮询,事件生产者将事件排队放入队列中,队列另外一端有一个线程称为事件消费者会不断查询队列中是否有事件,如果有事件,就立即会执行,为了防止执行过程中有堵塞操作影响当前线程读 ...

最新文章

  1. oracle测试环境表空间清理
  2. java跳过_java跳过https证书直接请求工具类
  3. 利用命令来打开所有程序,这个装逼给满分!
  4. mysql mybatis list循环_mybatis框架,使用foreach实现复杂结果的查询--循环List集合方式...
  5. python逐行写入文件_python逐行读写txt文件的实例讲解
  6. 编码格式(UTF-8 与 ANSI)各种编码解码(encode、decode)
  7. python selenium安装教程_Python Selenium模块安装使用教程详解
  8. jdk下没有java源码_openJDK之如何下载各个版本的openJDK源码
  9. 版本控制、协同开发工具比较
  10. 巧妙的实现 CSS 斜线
  11. php syslog服务器,Linux Syslog日志服务器的搭建
  12. 湘源控规计算土石方流程
  13. 组态王中Modbus字节顺序的调整
  14. 卸载精灵(bue directx) r4.0 完美版 是什么
  15. 产品目标拆解:结构化思维
  16. html js 做一个钟表,html,css,js实现的一个钟表
  17. ImageIO工具类简介及应用
  18. 程序功能:输入一个长整数n,从高位开始逐位分离并输出。 例如输入123456
  19. 【深度学习】生成对抗网络GAN|GAN、WGAN、WGAN-UP、CGAN、CycleGAN、DCGAN
  20. FPGA和CPLD的区别

热门文章

  1. UVa1592 数据库(摘)
  2. 在Seismic.NET下用最少的语句写出一个剖面显示程序
  3. 修改YUM源为本地光驱
  4. 7.企业应用架构模式 --- 分布策略
  5. 6.Shell 编程从入门到精通 --- 文件和文件系统
  6. 29.优化 MySQL Server
  7. 17.词法分析和语法分析
  8. 59. 预定义超全局变量
  9. 110个oracle常用函数总结(7),oracle110个最常用函数
  10. php中的分页类Page的用法