热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

明白异步JavaScript

写在文章前这篇文章是翻译自SukhjinderArora的UnderstandingAsynchronousJavaScript。这篇文章形貌了异步和同步JavaScript是怎样

写在文章前

这篇文章是翻译自Sukhjinder Arora的
Understanding Asynchronous Javascript。这篇文章形貌了异步和同步Javascript是怎样在运转环境中,运用挪用栈,音讯行列,功课行列,以及事宜轮回来事情的。文章若有翻译不好的处所还望多多见谅。

明白异步Javascript

尽人皆知,Javascript 是单线程的编程言语,那就意味着在同一个时刻只能有一件事发作。浅显的讲,Javascript引擎每一个线程一次只能处置惩罚一个声明。

虽然单线程言语能够简化写代码的历程,由于你不必忧郁并发的题目,但如许同时也意味着你没法在不锁住主线程的状况下,实行像收集接见这类长时刻的操纵。

设想一下,从API要求数据的这个状况。服务器能够须要一些时刻来处置惩罚要求,同时壅塞主线程使网页无相应。

这就是异步Javascript能够发挥作用的处所了。运用异步Javascript(比方像回调,promises,和async/await),你就能够在不锁住主线程的状况下实行长时刻的收集要求。

你没有必要进修一切这些观点来成为一个精彩Javascript工程师,这些只是对你很有协助罢了:)

所以空话不多说,我们最先吧。

同步Javascript是怎样事情的呢?

在我们深切相识异步Javascript之前,让我们先来相识一下同步的Javascript代码是怎样在引擎内部实行的。举个例子:

const secOnd= () => {
console.log('hello there');
}
const first = () => {
console.log('hi,there');
second();
console.log('The End');
}
first();

在我们想要明白上面代码是怎样在Javascript引擎实行的之前,我们须要先要明白实行上下文和挪用栈的观点(也叫实行栈)。

实行上下文

实行上下文是Javascript代码被评价和实行的处所的抽象观点。每当任何js代码实行的时刻,他们就运转在实行上下文内部。

函数实行在函数的实行上下文内,全局代码实行在全局的实行上下文内。每一个函数都有本身的实行上下文。

挪用栈

挪用栈就像他名字里展现的那样,他是一个具有后进先出的栈构造,它用于存储代码实行时期建立的一切实行上下文。

Javascript是具有单一挪用栈的,由于它是单线程的言语。挪用栈的LIFO(后进先出构造)决议了东西只能从栈的顶部增加或许删除。

让我们回到上面的代码片断,然后尝试明白一下上面的代码片断是怎样在Javascript引擎内部实行的。

const secOnd= () => {
console.log('hello there');
}
const first = () => {
console.log('hi,there');
second();
console.log('The End');
}
first();

上面代码的挪用栈:
《明白异步Javascript》)

所以究竟发作了什么呢?

当代码实行的时刻,一个全局的实行上下文就被建立了(示意为main())然后将他压入挪用栈的顶部。当first()被挪用的时刻,first()又被压入挪用栈的顶部。

接下来,console.log('hi,there')又被压入栈的顶部,当它实行完毕,他就从栈中弹出了。以后,我们挪用了second(),所以second()函数就被压入栈顶。

console.log('Hello there!')被压入栈顶,而且当它实行完毕就被弹出。 此时,second()函数实行完毕,所以从栈中弹出。

console.log('The End')被压入栈顶然后再完毕的时刻被移出。然后,first()函数实行完毕,被移出挪用栈。

此时,全部顺序完毕挪用,所以全局实行上下文(main())从栈中弹出。

异步Javascript究竟是怎样运转的呢?

如今我们已对挪用栈有个大抵相识了,也知道了同步的Javascript是怎样事情的,如今我们回到异步Javascript这个话题。

什么是锁?

我们设想一下我们正在运用同步的体式格局举行图象处置惩罚或许收集要求。比方:

const processImage = (image) => {
//对图象举行处置惩罚
console.log('Image Processed');
}
const netWorkRequest = (url) => {
//收集资源要求
return someData;
}
const greeting = () => {
console.log('Hello World');
}
processImage(logo.jpg);
networkRequest('www.somerandomurl.com');
greeting();

图象的处置惩罚和收集要求很花时刻。所以当processImage()函数被挪用的时刻,消费的时刻将取决于图象的大小。

processImage()函数完毕,将会被从挪用栈移出。以后networkRequest()函数被挪用而且被压入栈中。所以又要消费一些时刻来完毕挪用。

末了当networkRequest()函数完毕,greeting()函数被挪用,由于他只包含一个console.log声明,而且console.log声明实行的异常地块,所以greeting()函数很快的就完毕挪用了。

如你所见,我们必需要等,比及函数(就像processImage()networkRequest())完毕实行。这就意味着这些函数被锁在挪用栈或许主线程里。 所以在上述代码实行时期我们不能实行任何其他的操纵,这不绝不是我们想要的。

所以怎样处理?

最简朴的处理办法就是异步回调。我们运用异步回调让我们的代码不被锁住。举个栗子:

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

在这里我运用了setTimeout要领来模仿收集要求。请注意setTimeout不是Javascript引擎的一部分,它是Web Api(浏览器中)和 C/C++ (在node.js)中的一部分。

为了明白这段代码是怎样实行的,我们须要明白更多的观点,比方像事宜轮回和回调行列(也叫做使命行列或许音讯行列)。

《明白异步Javascript》

事宜轮回,WEB API, 音讯行列/使命行列不是Javascript引擎的一部分,他们是浏览器的Javascript运转时环境或许Node.js Javascript 运转环境的一部分。 在Nodejs中,收集接口被C/C++ API 庖代.

如今,让我们回到上面的代码,然后看一看他们是怎样以异步的体式格局实行的。

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

《明白异步Javascript》

当上面的代码在浏览器加载的时刻,console.log('Hello World')入栈而且当挪用完毕的出栈。接下来,挪用的是networkRequest(),所以它被推入栈顶。

接下来setTimeout()要领被挪用,所以被压入栈顶。setTimeout函数有2个参数:1) 回调函数 2)以ms为单元的时刻。
setTimeout在Web API环境中最先了一个为时2s的计时器。此时,setTimeout已完毕了,所以被弹出栈,接着,console.log('The End')被压入栈,实行然后在完毕后从栈中移出。

与此同时,计时器到时刻了,如今回调被推入到信息行列,但回调并没有被马上实行,而是被放到了事宜轮回最先的处所。

事宜轮回

事宜轮回的义务就是检察挪用栈并肯定挪用栈是不是为空。假如挪用栈为空,他就会检察音讯行列来肯定是不是有任何挂起的回调函数守候被实行。

在这个例子中音讯行列中包含一个回调函数,而且此时挪用栈为空。因而事宜轮回把回调函数压入栈顶。

在那以后,console.log(‘Async Code‘)这条语句被压入栈顶,实行,然后从栈中弹出。此时回调函数完毕了,所以它被从栈中弹出,然后全部顺序完毕实行。

DOM 事宜

音讯行列中也包含DOM事宜中的回调函数比方点击事宜和键盘事宜,比方:

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

在DOM事宜里,事宜监听器位于Web API 环境中守候某个事宜发作(在这个例子中是点击事宜),而且当该事宜发作的时刻,回调函数则被安排在音讯行列中守候被实行。

事宜轮回会再次搜检挪用栈是不是为空,假如为空的话,它会把事宜回调压入栈中,然后回调函数则被实行。

我们已进修了异步回折衷DOM 事宜是怎样实行的,他们运用音讯行列来存储一切守候被实行的回调。

ES6 功课行列/ 微使命行列

ES6引见了一种被Javascript 中Promises运用的叫做功课行列/微使命行列的观点。音讯行列和功课行列的区分就在于功课行列会比音讯行列具有更高的优先级,也就是说功课行列/微使命行列中的Promise的使命会比音讯行列中的回调函数先实行。

比方:

console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
},0);
new Promise((resolve,reject) => {
resolve('Promise resolved');
}).then(res => console.log(res))
.catch(err => console.log(err));
console.log('Script End');

输出:

Script start
Script End
Promise resolved
setTimeout

我们能够看到promise是在setTimeout之前被实行的,由于promise的返回是存储在微使命行列中的,它比音讯行列具有更高的优先级。

让我们看下一个例子,此次有两个Promises和两个setTimeout。

console.log('Script start'); setTimeout(() => {
console.log('setTimeout 1');
},0);
setTimeout(() => {
console.log('setTimeout 2');
},0);
new Promise((resolve,reject) => {
resolve('Promise 1 resolved');
}).then(res => console.log(res))
.catch(err => console.log(err)); new Promise((resolve,reject) => {
resolve('Promise 2 resolved');
}).then(res => console.log(res))
.catch(err => console.log(err));
console.log('Script End');

这一次输出:

Script start
Script End
Promise 1 resolved
Promise 2 resolved
setTimeout 1
setTimeout 2

我们能够看到两个promise都是在setTimeout回调的前面实行的,由于事宜轮回机制中,微使命行列中的使命要优先于音讯行列/使命行列中的使命。

当事宜轮回正在实行微使命行列中的使命时,假如另一个promise处于resolved的状况的话,他会被增加到同一个微使命行列的尾部,而且他会比音讯行列中的回调先实行,不论回调函数已守候实行了多久了。(优先级高果真就是能随心所欲= =)。

举个例子:

console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
new Promise((resolve, reject) => {
resolve('Promise 1 resolved');
}).then(res => console.log(res));
new Promise((resolve, reject) => {
resolve('Promise 2 resolved');
}).then(res => {
console.log(res);
return new Promise((resolve, reject) => {
resolve('Promise 3 resolved');
})
}).then(res => console.log(res));
console.log('Script End');

此次的输出:

Script start
Script End
Promise 1 resolved
Promise 2 resolved
Promise 3 resolved
setTimeout

所以一切在微使命行列中的使命都将在音讯行列中的使命之前实行。也就是说,事宜轮回将会在实行任何音讯行列的回调之前,起首清空微使命行列中的使命。

总结

我们已进修了异步Javascript是怎样事情的,以及一些其他的观点比方说挪用栈,事宜轮回,音讯/使命行列以及事情/微使命行列,他们在一起构成了Javascript的运转环境。再重申一下,虽然您没有必要将这些一切的观点都进修,来成为一个精彩的Javascript开发人员,但相识这些观点会很有协助:)

本日的文章就如许啦,假如你以为这篇文章对你很有协助,请点击旁边的拍手按钮,你也能够在Medium和Twitter上面follow我。假如你有任何的疑问,迎接在下面留言,我会很高兴的协助你的:)

译者结语

假如你对我的翻译或许内容有什么看法或许发起迎接在下面留言告诉我,喜好文章就给个赞吧,异常感谢您的浏览,Hava a nice day:)


推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
author-avatar
ThanksGiven
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有