理解Javascript函数执行


javascript是单线程单一并发语言,这意味着它可以一次处理一个任务或一次处理一段代码。它有一个调用堆栈,它与堆之类的其他部分一起构成了Javascript并发模型(在V8内部实现)。我们首先来看看这些术语:

调用堆栈

它是一个记录函数调用的数据结构,基本上是程序中的位置。如果我们调用一个函数来执行,我们将一些东西推送到堆栈,当我们从函数返回时,我们弹出堆栈的顶部。

正确输出= 100

当我们运行上面的文件时,我们首先查找所有执行将开始的main函数。在上面,它从console.log(bar(6))开始,它被推入堆栈,它上面的下一帧是带有它的参数的函数栏,它又调用函数foo,再次将其推送到堆栈的顶部,它立即返回,因此弹出堆栈,然后弹出类似的条,最后弹出控制台语句打印输出。所有这一切都在jiffy(毫秒)中一次发生。

你们所有人都必须在我们的浏览器控制台中看到长红色错误堆栈跟踪,它基本上表示调用堆栈的当前状态以及它在函数中的位置从上到下的方式失败,就像堆栈一样(见下图)

Error stack trace

有时,我们进入一个无限循环,因为我们递归地多次调用一个函数,而对于Chrome浏览器,堆栈的大小有16,000帧的限制,超过它只会为你杀掉东西并抛出Max达到堆栈错误(下图)。

Error stack trace

对象分配在堆中,即大多数非结构化的内存区域。变量和对象的所有内存分配都在此处进行。

队列

JavaScript运行时包含一个消息队列,它是要处理的消息列表以及要执行的关联回调函数。当堆栈具有足够的容量时,从队列中取出消息并进行处理,其中包括调用相关联的函数(从而创建初始堆栈帧)。当堆栈再次变空时,消息处理结束。基本来说,在给定回调函数的情况下,这些消息会响应外部异步事件(例如单击鼠标或接收对HTTP请求的响应)进行排队。例如,如果用户单击按钮并且未提供回调函数 - 则不会将任何消息排入队列。

事件循环

基本上,当我们评估JS代码的性能时,堆栈中的函数会使它变慢或快,console.log()会很快但是在数千或数百万行项目中执行迭代会更慢,并将保持堆栈占用或阻止。这被称为阻止脚本,您已在网页Speed Insights中阅读或听说过。

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

JS中的并发 - 一次一件事,除非真的,异步回调“

最简单的解决方案是使用异步回调,这意味着我们运行一些代码并给它一个稍后将执行的回调(函数)。我们都必须遇到异步回调,就像任何使用$ .get(),setTimeout(),setInterval(),Promises等的AJAX请求一样。节点都是关于异步函数执行的。所有这些异步回调都不会立即运行,并且会在一段时间后运行,因此不能像在console.log(),数学运算等同步函数中那样立即在堆栈内推送。他们去哪里,他们如何处理?

图1

如果我们在Javascript中看到一个网络请求,就像上面的代码一样:

  1. 执行请求函数,将onreadystatechange事件中的匿名函数作为回调执行,以便在将来某个时间响应可用时执行。
  2. “脚本调用完成!”立即输出到控制台
  3. 在将来的某个时间,响应将返回并执行我们的回调,将其主体输出到控制台。

调用者与响应的分离允许JavaScript运行时在等待异步操作完成以及触发其回调时执行其他操作。2这是浏览器API启动并调用其API的地方,这些API基本上是由在C ++中实现的浏览器创建的线程,用于处理DOM事件,http请求,setTimeout等异步事件。(在知道这一点之后,在Angular 2中,使用区域,monkey补丁这些API以引起运行时更改检测,我现在可以得到一张图片,他们是如何实现它的。)

浏览器Web API-由用C ++实现的浏览器创建的线程,用于处理DOM事件,http请求,setTimeout等异步事件。

现在这些WebAPI本身不能将执行代码放到堆栈上,如果确实如此,那么它会随机出现在代码中间。上面讨论的消息回调队列显示了方法。当完成执行时,三分之一的WebAPI将回调推送到此队列。事件循环现在负责在队列中执行这些回调并将其推入堆栈中,当它为空时4.事件循环基本工作是查看堆栈和任务队列,推送第一件事就是当堆栈显示为空时,队列到堆栈。在处理任何其他消息之前,将完全处理每个消息或回调。

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

图2

在Web浏览器中,只要事件发生并且附加了事件侦听器,就会添加消息。如果没有监听器,则该事件将丢失。因此,单击带有单击事件处理程序的元素将添加消息 - 与任何其他事件一样。调用此回调函数充当调用堆栈中的初始帧,并且由于JavaScript是单线程的,因此在堆栈上的所有调用返回之前,将停止进一步的消息轮询和处理。后续(同步)函数调用将新的调用帧添加到堆栈。


文章作者: 左智文
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 左智文 !
评论
  目录