JavaScript 的工作原理

2025-05-27

JavaScript 的工作原理

理解基础知识至关重要。所以,我们来讨论一下 JavaScript 的工作原理吧🤔

JavaScript 是如何工作的?🤷🏻‍♀

了解 JavaScript 的工作原理可以使阅读和编写代码变得更容易、更少挫败感,并允许您专注于应用程序的逻辑,而不是与语言的语法作斗争。

我们在文本编辑器中编写代码,不知何故,这些代码神奇地变成了 0 和 1,告诉计算机做某事。😇

Javascript 是一种单线程、解释型语言。

如果我将 JS 文件提供给 CPU 并要求在浏览器中执行它,它将无法理解该语言,因为上一台计算机只理解 0 和 1。我们如何使用 JS 文件进行通信,因此计算机在浏览器中执行代码。

这里,出现了 Javascript 引擎。

JavaScript 引擎🎰

JavaScript 引擎的工作原理

有了 JavaScript 引擎,我们就可以把 JavaScript 文件交给引擎。引擎会理解 JavaScript 文件,并告诉计算机如何处理代码。

从某种意义上说,您只是创建了一个翻译器,以便可以与不懂您的语言的人进行交流。

一共有 8 个引擎,它们被称为 ECMAScript。快速引擎是用 C++ 编写的 v8。

❓ 谁创建了第一个 JS 引擎 ❓

Brendan Eich。☺️ 在此之前,计算机只能理解 HTML 和 CSS 🤯

引擎内部发生了什么?

JavaScript 引擎的工作原理

当我们给出一个 Javascript 文件时,它首先进行词法分析(解析器),将代码分解为标记以识别其含义。

这些标记将在称为 AST(抽象语法树)的树中形成。

看看它是如何运作的。前往链接

一旦树形成,它就会进入解释器

解释器和编译器

在编程语言中,有两种方法可以将其翻译成机器语言,即计算机可以理解的语言。

解释器,我们逐行翻译并阅读文件。

编译器,它会提前翻译我们所写的代码,并将其编译成我们的机器可以理解的语言。

解释器和编译器语言

在上图中,我们在 Javascript 中有一个高级语言,解释器逐行获取高级语言代码并输出字节码。

编译器会将高级语言代码转换成机器码。这样,它就可以将机器码交给 CPU,然后 CPU 就可以真正运行这些代码了。

因此,解释器允许我们立即运行代码,而编译器和分析器允许我们在运行时优化代码。

Babel + TypeScript ḆṮ

babel 和 typescript

Babel是一个 Javascript 编译器,它接受现代 Javascript 代码并返回与浏览器兼容的 JS(较旧的 JS 代码)。

Typescript是 Javascript 的超集,可编译为 Javascript。

这两者都完全按照编译器的功能进行操作:将一种语言转换为另一种语言!

解释器和编译器的优缺点:

  1. 编译器需要更长的时间才能启动和运行,但代码最终运行得更快。
  2. 解释器启动和运行速度非常快,但没有进行任何优化。

❓ 有什么方法可以让我们两全其美吗?❓

是的,谷歌带来了V8发动机,它结合了解释器和编译器,称为JIT(Just In Time)编译器,以使引擎更快。

使用分析器因为代码是通过解释器运行的,解释器会告诉浏览器如果同一行代码运行多次该如何处理。我们实际上将部分代码传递给编译器/JIT编译器,编译器会获取代码并进行编译或修改。

JavaScript 是解释型语言吗?

是的,JavaScript 刚出来的时候,就有像 Spider Monkey 这样的 JavaScript 引擎,它会把 JavaScript 解释成字节码,然后告诉浏览器该做什么。但现在我们也用编译器来优化代码。

内存堆和调用栈

内存堆和调用栈

内存堆是存储所有信息和写入信息的地方。这样我们就有地方分配内存、使用内存和释放内存。

调用堆栈需要跟踪我们在代码中的位置。

堆栈溢出

递归是造成堆栈溢出或大量函数嵌套的最常见方法之一,以保持堆栈不断增长。🤯

错误将出现如下:

未捕获的 RangeError:超出最大调用堆栈大小

垃圾收集⃥

Javascript 是一种垃圾收集语言。

这意味着当我们创建任何对象时,如果执行之后我们不再需要该对象,那么它将为我们清理它。

Javascript 会自动释放我们不再使用的内存。

❓❓JavaScript 中的垃圾收集是如何工作的?❓❓

⇒ 它使用标记和清除算法。

JavaScript 的工作原理

内存泄漏

内存泄漏是应用程序过去使用过的一块内存,但不再需要它,但尚未返回给我们,成为可怜的空闲内存。

运行下面的代码片段,我们将运行一个无限循环,不断地推动 i-1,直到我们的内存填满,并且没有任何可用的东西,这会导致我们的浏览器崩溃。

例子:

let array = [];
for(let i = 5; i > 1; i++) {
    array.push(i-1);
}
Enter fullscreen mode Exit fullscreen mode

以下是一些发生的内存泄漏:

  1. 不要有太多的全局变量
  2. 事件监听器

    当您不需要下面的 addEventListener 时,切勿删除它们。因此,请继续添加事件监听器。

    var el = document.getElementById('button')
    el.addEventListener('click', onclick)
    
  3. setInterval
    会持续运行,所以当不需要它们时我们需要使用 clearInterval 。

单线程🧵

控制台日志窗口

JavaScript 是单线程语言,因为它只有一个调用堆栈。调用堆栈允许我们一次运行一段代码,并且由于 JavaScript 是同步的,所以一次只能发生一件事。

运行代码的不仅仅是 JS 引擎,Javascript 运行时还可以处理正在运行的任务。

Javascript 运行时🏃🏻‍♂️

Web 浏览器在后台运行,而同步 JavaScript 代码正在运行,并使用 Web API 进行通信。因此,JavaScript 引擎知道有一些数据需要在后台处理。

Web API 随浏览器而来。这些 Web API 可以做很多事情,比如发送 http 请求、监听 DOM 事件、使用回调延迟执行、数据库存储。

例子:

如果您控制台日志窗口,您将了解浏览器提供了哪些属性。

console.log(window)
Enter fullscreen mode Exit fullscreen mode

控制台日志窗口

浏览器使用C++语言完成以上所有操作。

这些 Web API 被称为异步的。

setTimeout因此,如果出现任何回调或 Web API 调用,call stack事件循环将无法理解如何处理,因此它会将回调发送给 Web API,Web API 会自行处理。Web API 处理完回调后,会将其发送到回调队列,事件循环将从现在开始处理。事件循环会与调用栈和回调队列进行通信,如果调用栈为空,则将回调队列任务添加到调用栈。

例子:

console.log("1");
setTimeout(()  {
    console.log("2")
}, 1000)
console.log("3")

// OUTPUT: 
// 1
// 3
// 2
Enter fullscreen mode Exit fullscreen mode

让我们看看上面的例子如何运行:

我们将第一个控制台添加到调用堆栈,然后登录到控制台,然后从调用堆栈中删除该代码。

现在,添加了setTimeout调用堆栈,它立即认为那setTimeout是 Web API,所以call stack不知道如何处理它,所以call stack将发送setTimeout到 Web API。

然后我们转到下一行,检查其控制台日志,然后登录到控制台,然后从调用堆栈中删除该代码。

现在在 Web API 后面,它将启动 1 秒的计时器,一旦 1 秒结束,它将推送回调,即console.log("2")。然后console.log("2")将被推送到回调队列,然后持续运行的事件循环将检查调用堆栈是否为空?

事件循环仅当调用堆栈为空且已读取整个 JS 文件时才会运行。因此,在调用堆栈为空之前,事件循环不会将回调队列中的任何内容放入调用堆栈。

一旦清除,事件循环将会采取console.log("2")并打印。

参考🧐

🌟推特 👩🏻‍💻suprabha.me 🌟 Instagram
文章来源:https://dev.to/suprabhasupi/how-javascript-works-4ked
PREV
通过玩游戏学习 CSS
NEXT
GitHub 上 10 个 JavaScript 开发者仓库