javascript的回调函数-「翻译」理解Javascript函数执行——调用堆栈、事件循环、任务等。

2023-08-23 0 6,072 百度已收录

如今,Web 开发人员(我们更喜欢称为后端工程师)可以用脚本语言做任何事情,从在浏览器中提供交互,到开发笔记本游戏、桌面工具、跨平台联通应用,甚至部署在服务器上端(例如最流行的 Node.js)连接到任何数据库。 因此,了解 Javascript 的内部结构非常重要,这样才能优化、高效地使用它。 这也是本文的主要目的。

Javascript的生态变得越来越复杂。 要构建现代 Web 应用程序,您将不可避免地使用 Webpack、Babel、ESLint、Mocha、Karma、Grunt...我应该使用哪一个? 那些是做什么用的? 我发现这部动画完美地体现了当今网络开发人员的严峻困境:

Javascript 疲劳 - 学习 Javascript 是什么感觉

在深入框架和库的海洋之前javascript的回调函数,每个 Javascript 开发人员首先需要了解 Javascript 的底层是如何实现的。 几乎每个 JS 开发人员都听说过“V8”这个术语,但有些人甚至可能不知道它的含义以及为什么使用它。 在我职业发展的第一年,我对花哨的东西知之甚少,更关心的是先完成工作。 但这并不能满足我的好奇心,我想知道Javascript是如何做到这一切的。 为了决定更深入地挖掘,我通过 Google 进行了搜索,发现了一些优秀的博客,包括 Philip Roberts 的 JSConfontheeventloop 的精彩演讲。 所以我决定总结一下我的学习经历,分享给大家。 由于要学习的东西太多,我将本文分为两部分。 本部分介绍常用术语,第二部分探讨这些术语之间的关系。

Javascript是一种单线程、单并发的语言javascript的回调函数,这意味着它一次只能处理一个任务并执行一段代码。 它的调用栈与堆和队列一起构成了Javascript并发模型(在V8中实现)。 我们来一一看看这些词。

javascript的回调函数-「翻译」理解Javascript函数执行——调用堆栈、事件循环、任务等。

JSModel 的可视化表示

调用栈(CallStack):它是一个数据结构,记录了我们在程序中调用的函数。 如果我们调用一个函数来执行,我们会将某种记录下沉到调用堆栈的顶部; 当我们从函数返回时,我们从调用堆栈的顶部弹出记录。

JSStack可视化

javascript的回调函数-「翻译」理解Javascript函数执行——调用堆栈、事件循环、任务等。

当我们运行上图中的代码时,我们首先会找到所有执行的开始——main函数。 上面的例子中,一系列的执行都是从console.log(bar(6))开始的,所以这次执行被扔进了调用栈,前面一层是函数bar及其参数,函数bar依次调用function foo , foo 也被拖入栈中; 然后 foo 返回某个值,因此它会从调用堆栈中弹出; 同样,bar立即弹出,最后console语句复制结果并弹出。 所有这些动作都在眨眼间按顺序发生。

大家一定都见过浏览器控制台中那个又长又红的错误堆栈。 它使用类似从上到下堆栈的方法来简单地指示调用堆栈的当前状态以及函数中报告错误的位置(见右图)。

错误堆栈跟踪

有时,当我们以递归的方式多次调用一个函数时,我们会陷入无限循环。 对于 Chrome,其调用堆栈大小的限制为 16,000 层。 如果超出限制,程序将被终止并抛出。 达到堆栈限制错误(见右图)。

堆:对象在堆上分配 - 内存中的松散结构。 变量和对象的所有内存分配都是在堆上完成的。 队列:Javascript运行时,包括消息队列,它是一系列要处理的消息和要执行的相关反弹函数。 当调用堆栈有足够的空间时,将从队列中取出一条消息并进行处理,该消息调用关联的函数(并为此目的形成一个初始化堆栈层)。 当堆栈再次清空时,消息处理结束。 简而言之,这些消息被排队,指定弹跳函数来响应外部异步事件(例如键盘点击或响应 HTTP 请求)。 例如,如果用户单击没有相应后备功能的按钮,则不会将任何消息加载到队列中。

事件循环

当我们评估JS代码的性能时,我们必须知道调用堆栈中的函数会让程序变得更快或更慢,console.log()会很快,但是用for或while迭代数千次会更慢,但是let 调用堆栈仍然被占用和阻塞。 这称为阻塞脚本,您可能在 WebpageSpeedInsights 中见过。

网络请求会慢,图片请求也会慢,但好在服务请求可以通过AJAX等异步功能来完成。 如果这些网络请求是用同步函数完成的怎么办? 网络请求被发送到服务器——服务器只是某处的某种机器,现在假设服务器返回的响应可能是平淡的,此时,如果我点击一些CTA(号召性用语)按钮,或者一些其他的需求渲染完成后,不会有任何响应,因为调用堆栈仍然被之前的网络请求阻塞。 在像Ruby这样的多线程语言中,这些情况是可以控制的,但是在像Javascript这样的单线程语言中,除非调用堆栈中的函数返回一个值,否则它总是会被阻塞。 浏览器中没有任何反应,网页崩溃。 这样,我们就无法为最终用户提供流畅的用户界面。 那么我们该怎么办?

“JS 中的并发 - 一次只做一件事,除了异步反弹”

最早的解决方案是使用异步弹跳,也就是说我们在某部分代码中添加弹跳,待这段代码执行完成后才执行。 我们一定都遇到过异步反弹,比如 $.get()、setTimeout()、setInterval() 和 AJAX 请求的 Promises。 Node基于异步函数执行。 所有这些异步反弹不会像 console.log() 这样的同步函数那样立即运行,而是会在稍后运行,因此它们不会立即被推送到调用堆栈上。 他们去哪儿了? 如何控制它们?

如上面的示例,如果网络请求在 Javascript 中运行:

1. 请求函数被执行,给`onreadystatechange`事件传一个匿名函数作为回调,用来在将来响应就绪的时候执行。
2. “Script call done!”立刻输出到控制台。
3. 后续某时刻,响应被返回,回调被执行,响应体被输出到控制台。

javascript的回调函数-「翻译」理解Javascript函数执行——调用堆栈、事件循环、任务等。

反应式前馈调用允许 Javascript 运行时在等待异步操作完成和取消执行时执行其他操作。 浏览器插入并调用它的API,这是一个用C++实现的API,用于创建线程来控制DOM事件、http请求、setTimeout等异步事件。

这些 Web 套接字无法自行将执行代码放入调用堆栈中。 如果可以,那么套接字将随机出现在您的代码中(执行顺序不可控)。 前面讨论的消息退回队列说明了这一点。 任何 Web 套接字在完成执行后都会将反弹放入此队列中。 风暴循环现在负责控制队列中反弹的执行,并在堆栈为空时将反弹放入堆栈中。 风暴循环的基本工作是监听调用堆栈和任务队列,当它看到堆栈为空时,将队列中的第一个任务放入堆栈。 每条消息或退回邮件都会在前一个任务处理完成后进行处理。

while (queue.waitForMessage()) {
 queue.processNextMessage();
}

JavascriptEventLoop视觉表示

在网络浏览器中,一旦发生风暴并且绑定了风暴错误,消息就会立即添加到队列中。 如果没有bug,就意味着风暴输了。 因此,点击一个绑定了暴风雨处理程序的按钮就会添加一条新消息,其他暴风雨也是如此。 它反弹到的调用将是调用堆栈中的初始层,并且由于 Javascript 是单线程的,因此后续消息协程和处理将暂停,直到调用堆栈中的所有调用都返回。 后续(同步)函数调用将在调用堆栈中向新的调用级别下降。

下一部分我会用一个动画来展示上述过程的代码执行过程,并深入讲解异步函数有哪些不同类型、队列中谁先执行、以及零延迟等函数的方法。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript的回调函数-「翻译」理解Javascript函数执行——调用堆栈、事件循环、任务等。 https://www.wkzy.net/game/144055.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务