JavaScript 的工作原理是什么?🧐
没有什么比第一次学习编程更令人兴奋的了。我们热情高涨,恨不得马上开始编写项目。然而,我们往往忽略了真正能让代码更有意义的一点……那就是基础知识。
咱们开始吧?别忘了做笔记,这样有助于理解哦:)
首先,什么是 JavaScript?
JavaScript 是一种单线程语言,但它可以是非阻塞的。“嗯……这是什么意思呢?”单线程意味着它一次只能执行一组指令。你可以把它想象成阅读食谱,你必须从上到下逐条执行,对吧?它不会同时执行多个任务,因为它只有一个调用栈。
调用栈和内存堆
-
调用栈让 JavaScript 引擎能够跟踪函数调用,但更重要的是,它允许我们一次执行一个代码。简而言之,当你调用一个函数时,解释器会先将其添加到调用栈中,然后开始执行该函数,函数执行完毕后,它会从调用栈中移除。
-
要调用函数,它们必须存储在某个地方……而这个地方就是内存堆。内存堆是一种应用于计算机内存的资源管理机制。它用于根据程序的请求动态地分配内存,并在不再需要时释放内存以供重用。
内存泄漏
这就是计算机程序错误地管理内存分配时会发生的情况,即内存不再需要时没有被释放。
好消息是,JS Engine 引入垃圾回收器后,这个问题就得到了解决。
垃圾收集器(GC)
垃圾回收是一种自动内存管理机制。它会尝试回收程序已分配但不再需要的内存。
Stack Overflow
你可能听说过栈溢出吧?既然我们知道只有一个调用栈,那就意味着我们的资源也相当有限。当调用栈空间被超出限制时,就会发生栈溢出。
但是如何做到呢?
最常见的原因是无限递归,即一个函数调用自身如此之多,以至于存储每次调用相关的变量和信息所需的空间远远超过了堆栈所能容纳的空间。
或许举个例子会有帮助 :)
function hello() {
console.log("Helloo, you are awesome!!!");
hello();
}
hello();
这里,我们创建了一个无限循环,它会无限期地运行下去。如果我们在浏览器中运行它,该函数会一直运行,直到超出堆栈空间为止,如下图所示👇。
你还在跟上吗?太好了,我们继续!
既然我们已经了解了 JavaScript 是什么,接下来我们需要了解它是如何工作的。
JavaScript 引擎
正如你可能知道的,计算机只能理解二进制代码(0 和 1),这意味着它们无法直接执行我们的 JavaScript 文件,因为它们无法理解文件内部编写的内容。而这正是JS 引擎发挥其最重要作用的地方。
我们首先需要一个转换器,让计算机理解我们的意图,而不是直接将JS文件发送到CPU。正如上图所示,引擎接收我们的文件,将其编译成二进制指令,然后发送到CPU最终运行。
你可知道?
第一个 JavaScript 引擎由Brendan Eich于 1995 年创建。在此之前,浏览器几乎没有交互性,因为它们只能用 HTML 和 CSS 运行。
JavaScript 引擎内部
当我们运行一个 JS 文件时,它首先会经过解析器,解析器会执行词法分析,这意味着它会将代码分解成标记,以识别它们的含义或了解代码试图做什么。
其次,这些标记将被构建成抽象语法树(AST)。AST是计算机程序源代码的树状表示。
现在,在继续之前,我想谈谈 JS 引擎中经常让人感到困惑的两件重要的事情,那就是……解释器与编译器。
解释器与编译器
在编程中,通常有两种方法可以将代码翻译成机器语言,我们先从第一种方法开始。
-
解释器会逐行读取并翻译文件。虽然这个过程可能比较慢,但程序运行速度很快,您可以立即发现任何错误。
-
与解释器不同,编译器不会立即开始翻译。它会预先进行编译,生成相应的代码。简而言之,编译器无需等待程序逐条执行指令,而是会一次性读取整个文件并执行。虽然这需要一些准备时间,因为编译器需要先读取所有指令,但之后运行速度会非常快。
你可知道?
JavaScript 最初是一种解释型语言,但现在我们可以使用编译器来优化代码。
现在你可能想知道应该使用编译器还是解释器,但你猜怎么着,两者兼备才是王道!谷歌开发的 V8 引擎就同时使用了解释器和编译器,也就是所谓的JIT(即时)编译器,以提升引擎运行速度。
太好了!既然我们已经了解了 JavaScript 是什么以及它是如何工作的,但还有一个小问题。如果 JavaScript 只有一个调用栈,并且一次只执行一条指令,那么运行一个大型程序岂不是要花很长时间?嗯,没错,但这就是JavaScript 运行时发挥作用的地方!
什么是 JavaScript 运行时?
运行时环境是编程语言执行的环境。这将使浏览器在后台持续运行,同时同步的 JavaScript 代码也在运行,它使用Web API进行通信。
记住💭
需要注意的是,运行时环境会根据上下文而有所不同。例如,浏览器运行时环境可能与……截然不同Node.JS。然而,这些差异主要体现在实现层面,这意味着以下概念仍然适用。
Web API
首先,什么是API?
API是应用程序编程接口的缩写。简而言之,它是一种接口,包含一组函数,允许程序员构建软件/应用程序,或访问应用程序的某些特定功能或数据。
现在,Web API并非 JS 引擎的一部分,而是由浏览器提供,它通过执行诸如发送 HTTP 请求、监听 DOM 事件、使用回调延迟执行等多种任务来扩展浏览器的功能……
这些Web API被称为异步API。为什么呢?因为它们可以按照与JS不同的顺序执行指令,而JS只能从上到下执行。
它是如何运作的?
使用setTimeout,您可以延迟第一条指令的执行,以便先执行第二条指令。
我们来看一下:
console.log("First");
setTimeout(() => {
console.log("Second");
}, 1000 );
console.log("Third");
/*
OutPut: First,
Third,
Second
*/
如上例所示,console.log("Second")被写成了第二个,但在输出中却排在最后。
但是如何做到呢?
- 好的,这里发生的情况是,我们运行了console.log("First"),然后设置console.log("Second")延迟1 秒。
- 由于setTimeout是 Web API 中的一个函数,调用堆栈无法识别它,因此会将该函数发送给 Web API 进行处理。(在发送 setTimeout 函数的同时,它会继续执行 **console.log("Third") **)
- 最后,Web API 将读取 setTimeout 函数,然后等待我们定义的1 秒延迟,遍历回调队列,最后返回调用堆栈。
回调队列
回调队列是一种遵循先进先出(FIFO )原则的数据结构。所有异步回调函数在被发送到调用栈之前,都必须先进入回调队列。队列会尽量保持消息或方法添加到队列的顺序。
事件循环
事件循环会持续检查调用栈是否为空以及事件队列中是否还有消息。其目的是将消息从回调队列移至调用栈,但仅当调用栈为空时才会执行此操作。
希望你喜欢这篇文章❤️
好了,信息量确实很大,如果你读到这里,恭喜你!如果你以后想再来查阅,可以把这个页面收藏起来。
如果你喜欢这篇文章,请点赞或留言告诉我:) 或者如果你有想让我写的主题,也请告诉我。
感谢您抽出时间。
祝您编程愉快 :)
文章来源:https://dev.to/klc/how-does-javascript-work-3fpl







