2

JavaScript 到底是如何执行的呢 -- JS的作原理

 2 years ago
source link: https://bajie.dev/posts/20220621-javascripts_how_to_run/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript 到底是如何工作的?

一、工作原理

JavaScript到底是:

  • 同步还是异步?
  • 单线程还是多线程?
  • JavaScript 中的一切都发生在

    Execution Context (执行上下文)中

    • 您可以假设这个执行上下文 是一个大盒子或一个容器,在其中执行整个 JavaScript 代码。
    • 这个大盒子里有两个组件:
      • Memory(内存组件):这是所有变量和函数存储为键值对的地方。这个**“内存组件”**也称为**变量环境**。因此,它是一种环境,其中所有这些变量和函数都存储为键值对。
      • Code(代码组件):这是代码逐行执行的地方。这个“代码组件”也称为执行线程。所以,这个执行线程是一个单线程,整个代码一次只执行一行。
  • 结论:JavaScript 是一种同步单线程语言。

    • 单线程 意味着 JavaScript 一次只能执行一个命令。
    • 同步单线程 意味着 JavaScript 一次只能以特定顺序每次执行一个命令。这意味着它只能在当前行完成执行后转到下一行。这就是同步单线程的意思。

很惊诧吧,实际 javascripts 有单线程 event loop 大循环来完成很多不可思议的事情。

二、实际工作过程分析

JavaScript 代码是如何执行的?

当你运行 JavaScript 代码时会发生什么?

会创建一个Execution Context(执行上下文)

让我们使用实际的代码来举个例子:

var n = 2;
function square(num) {
  var ans = num * num;
  return ans;
}
var square2 = square(n);
var square4 = square(4);
fallback
  • 执行上述代码时,会创建 一个执行上下文

    • 此执行上下文分两个阶段创建:

      • 一、创建:

        创建阶段也称为内存创建阶段

        。这是一个非常关键的阶段。

        • 内存创建的第一阶段,JavaScript 会为所有的变量和函数分配内存。

        • 首先 JavaScript 遇到var n = 2;,它就会分配内存给n.

          当为n分配内存时,它会先存储一个特殊值undefinedundefined在 JavaScript 中被视为特殊的占位符。

        • 在遇到 functionsquare(num)时,它也会为这个 function () 分配内存。

        • 在为 function() 分配内存的情况下,它将该函数的整个代码存储在内存空间中。

        • 后面为两个变量square2square4分配了内存,存储的同样是undefined

        • 为了完成这个创建阶段,JavaScript 会逐行从上到下遍历扫描代码。

      • 二、代码执行:

        • 扫描完毕,现在 JavaScript 再次逐行运行整个程序。
        • 当它遇到时var n = 2;,它实际上将2作为值n放入内存组件中。
        • 当它遇到 的函数定义时square(num),它没有什么可执行的,所以 JavaScript 简单地跳过了。
        • 当它遇到 时var square2 = square(n);,我们现在正在调用一个函数。
        • 函数是 JavaScript 的核心。它们在 JavaScript 中的行为与在任何其他语言中的行为非常不同。
        • 每当调用一个函数时,都会创建一个全新的Execution Context(执行上下文)
        • 因此,从技术上讲,在整个**Execution Context 的代码组件中又创建了一个全新的Execution Context ** 。
        • 这个新的内部 Exection Context 执行上下文也有它自己的内存组件代码组件
        • 现在在内部发生的事情是:
          • 在这种情况下,有 2 个变量,即num(参数)和ans
          • 所以内存将分配给numans
          • 在第 1 阶段,与外部执行上下文一样,undefined将分配给numans
          • 现在进入阶段 2(代码执行阶段),参数的值被分配给参数。因此,在我们调用函数的语句var square2 = square(n);时,我们将参数n的值 2 传递给函数square(num),并且该参数的值替换了内部执行上下文num内存组件中的占位符undefined
          • 计算后num * num,将值存储在 中ans
          • 在遇到 时return ans;,将存储的值ans返回到调用的位置,并且此内部执行上下文结束。当它结束时,内部执行上下文实际上被删除了。
        • 现在,遇到下一行时遵循相同的过程var square4 = square(4);
        • 最后一行成功执行后,整个执行上下文也被删除。这种*“整体”*执行上下文也称为**全局执行上下文**。

那么,JavaScript 是如何管理这种链条式的 ** 执行上下文 ** 的呢?

  • 它实际上在后台管理一个堆栈。
  • 此堆栈也称为Call Stack(调用堆栈)
  • GEC(Global Execution Context 全局执行上下文)始终位于此堆栈的底部。
  • 每当创建一个新的执行上下文时,它就会被压入这个堆栈,并在完成其目的时被弹出。
  • 控件与此调用堆栈的最顶部元素保持一致。
  • 调用堆栈仅用于管理执行上下文
  • 在成功执行最后一条语句时,调用堆栈被清空。

调用堆栈维护执行上下文的*执行顺序。*

给个图更好理解:

image-20220621105516197

调用堆栈具有以下花哨的名称,也可以通过这些名称来引用它:

  • Execution Context Stack(执行上下文堆栈)
  • Program Stack(程序栈)
  • Control Stack(控制堆栈)
  • Runtime Stack(运行时堆栈)
  • Machine Stack(机器堆栈)

这样大家就理解了吧,这样后面的变量提升 Hoisting 就很好理解了。



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK