这篇文章的真正目的是以一种简单的方式介绍 JavaScript 在底层如何工作,这样即使是新程序员也能够掌握这个概念并可视化编写 JavaScript 时会发生什么。
首先,我想关注至少 3 个问题,这将有助于克服困难并内化背后的逻辑?
这些问题也是 Web 开发人员面试期间可能会被问到的问题,其中 JavaScript 意味着:
1。 JavaScript 是如何工作的?
2.解释一下同步和异步的区别?
3.或者解释一下这句话:JavaScript是单线程语言,可以是非阻塞的?
确实,编写程序并不需要了解 JavaScript 内部工作原理,但学习 JavaScript 是必要且至关重要的,以便了解背后发生的事情并感受您正在编写的内容,因此对于许多拥有多年经验的开发人员来说运营商不想知道这一点。
让我们先知道什么是程序?程序只是一组指令,告诉计算机要做什么以及如何执行任务。程序必须分配内存,否则,我们将无法在计算机上拥有变量,甚至无法保存文件。程序还应该解析(读取)并执行专用任务,并且所有操作都发生在内存中。
现在,JavaScript 拥有每个浏览器都实现的名为 JavaScript 引擎 的引擎。例如,在 Chrome 中,它称为 V8,在 Mozilla Firefox 中:Spider Monkey,Safari 浏览器:JavaScript Core Webkit。
下图为 google chrome 的 V8 引擎
JavaScript 引擎内部发生了什么?
JavaScript 引擎(例如 Chrome 中的 V8)读取我们编写的 JavaScript 代码,并将其转换为浏览器的机器可执行指令。上图显示了 JavaScript 引擎的各个部分,它由两部分组成,即内存堆**和**调用堆栈。
还需要注意的是,内存分配发生在内存堆中,而解析(读取)和执行发生在调用堆栈中。除此之外,内存堆告诉你你在程序中的位置。
让我们用 JS (JavaScript) 代码看看内存堆中的内存分配
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
那么,上述代码在全局声明后会出现什么问题呢?
有一种叫做内存泄漏的东西。如上所述,变量声明发生在内存堆中,并且它的分配大小是有限的。当您继续声明非常大的数组而不是数字甚至未使用的全局变量时,这会填满内存并导致内存泄漏。你会听说全局变量很糟糕,因为当我们忘记清理时,我们会填满这个内存堆,最终浏览器将无法工作。
调用堆栈怎么样?
如果我们还记得的话,读取和执行脚本的是调用堆栈。我们用代码来说明一下吧。
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
使用上面的代码,调用sack读取第一行console.log(“x”);并被放入调用堆栈中,JavaScript引擎识别出console.log已被添加,然后将其弹出到调用堆栈中,运行它并输出x。之后,它会删除第一个 console.log,因为它已完成运行,并将其放入第二个 console.log(“y”),将其添加到调用堆栈中,执行 y 并删除第二个 console.log。最后使用相同的过程获取console.log(“z”)。
这是最简单的演示,如果再复杂一点怎么办?举个典型的例子:
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z
//调用堆栈
函数example1() 将首先运行,然后函数 example2() 出现在调用堆栈的顶部并运行,在检查是否存在后打印出数字 7 作为输出其他要运行的代码。之后,它将开始按从 console.log(‘7’)、example2()、example1() 开始的顺序从调用堆栈中删除,并且调用堆栈现在为空。
>我们还记得这句话吗? JavaScript 是一种非阻塞的单线程语言。
单线程意味着它只有一个调用堆栈。它一次只能执行一件事,重要的是要强调调用堆栈是先进后出的,就像堆栈。
其他语言可以有许多调用堆栈,即所谓的多线程,拥有多个调用堆栈可能更有利,这样我们就不必一直等待任务。
>但是,为什么 JavaScript 被设计为单线程呢?
要回答这个问题,通常在单线程上运行代码非常容易,因为多线程环境中不会出现复杂的场景。你实际上有一件事情需要关心。在多线程中,可能会出现死锁之类的问题。有了这个理论,我们就很容易知道同步编程意味着什么。
同步编程简单来说就是:执行第一行代码,执行第二行代码,执行第三行代码,等等......
更明确地说,这意味着 console.log(“y”) 无法运行,直到 console.log(“x”) 完成并且 console.log (“z”) 直到前两个都完成后才开始,因为它是一个 调用堆栈。
程序员很可能会使用 stackoverflow.com 网站。这个名字是什么意思?出色地。让我们看看:
堆栈溢出是如何发生的
上图显示了内存泄漏是如何发生的以及 JavaScript 引擎的内存堆如何溢出。这里,调用堆栈接收许多大于其大小的输入并溢出。
可以借助代码来演示堆栈溢出:
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
请注意,JavaScript 是单线程的,一次只执行一个语句。 现在有一个问题:如果下面代码块中的console.log(“y”)有一个需要更长时间才能完成的大任务怎么办?例如循环遍历具有数千或数百万项的数组?那里会发生什么?
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z
第一行将执行,并假设第二行有大量工作要执行,因此第三行将等待很长时间才能执行。在上面的示例中,这没有多大意义,但让我们想象一个执行繁重操作的大型网站,用户将无法执行任何操作。网站将冻结,直到任务完成并且用户在那里等待。对于表演来说这是一次糟糕的体验。
嗯,对于同步任务,如果我们有一个函数需要花费很多时间,那么它就会阻塞队列。所以,听起来我们需要一些非阻塞的东西。请记住我上面提到的那句话:JavaScript 是一种可以非阻塞的单线程语言。
理想情况下,在 JavaScript 中我们不会等待需要时间的事情。那么,我们该如何解决这个问题呢?
作为救援,有异步编程。那么,这是什么?
将异步视为一种行为。同步执行很棒,因为它是可预测的。在同步中,我们知道首先发生什么,接下来发生什么等等,但它可能会变慢。
当我们必须执行图像处理或通过网络发出请求(例如 API 调用)等操作时,我们使用的不仅仅是异步同步任务。
让我们看看如何用代码进行异步编程:
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
现在,根据上面的代码,我们似乎跳过了第二行并执行第三行,并等待 3 秒输出结果。这是异步发生的。
为了理解这一点以及发生了什么,让我们使用下图。
JavaScript 运行时环境
为了运行 JavaScript,我们需要的不仅仅是内存堆和调用堆栈。我们需要所谓的 JavaScript Run-Time,它是浏览器的一部分。它包含在浏览器中。在引擎之上,有一些称为Web API,回调队列和事件循环,如图所示。
现在我们来讨论一下使用 setTimeout 函数的代码。
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z
setTimeout 函数 是 Web API 的一部分,而不是 JavaScript 的一部分,相反,它是浏览器提供给我们用来进行异步编程的函数。因此,让我们提供更多详细信息以进行澄清。
调用堆栈: console.log(“x”) 进入调用堆栈,运行,然后我们将 console.log 发送到浏览器。之后,setTimeout(() =>{console.log(“y”);},3000);进入调用堆栈,因为第一个任务完成,然后转到第二个任务。
现在有件事,在阅读代码时,调用堆栈会检测到有一个 setTimeout 函数 已被设置,它不是 JavaScript 的一部分,而是 Web API 的一部分(参见图 JavaScript 运行时环境)并具有其特殊的特性。发生的情况是 setTimeout 触发 WEB API 并且由于 Web API 收到通知,该函数将从调用堆栈中弹出。
现在,Web API 启动一个三秒的计时器,知道它必须在 3 秒内完成任务。请记住这里,因为调用堆栈是空的,JavaScript 引擎继续到第 3 行,即 console.log(“z”);并执行它。这就是为什么我们得到结果 x,z 但我们在 Web API 中设置了三秒的 setTimeout。然后,三秒后,当时间限制结束时,setTimeout 运行并查看其中的内容,然后就完成了。完成后,Web API 将识别出它有 setTimeout 的 callback() 函数,并将其添加到 CALLBACK QUEUE 准备运行它。
我们来到最后一部分,即**事件循环*。这个函数会一直检查调用堆栈是否为空。当它为空并且 JavaScript 引擎中当前没有运行任何内容时,它将检查回调队列并使用 console.log(“z”) 找到我们的 **callback()* 函数,然后将其放入 CALL STACK 并运行。完成后,将其从调用堆栈中弹出。现在一切都是空的并得到结果 x z y。
结论:在这篇文章中,我们看到了很多有关幕后发生的事情的信息,以完全理解 JavaScript 逻辑以及同步和异步执行的任务。
希望这将帮助新的和高级 JavaScript 程序员享受在 ReactJS 或 AngularJS 等 JavaScript 相关框架中进行编码,因为这是理解高级逻辑的基础。
>快乐编码
参考文献
https://www.freecodecamp.org/news/how-javascript-works-behind-the-scenes。
https://www.simplilearn.com/tutorials/javascript-tutorial/callback-function-in-javascript#
以上是JavaScript 的底层是如何工作的?的详细内容。更多信息请关注PHP中文网其他相关文章!