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

JavaScript 到底是如何工作的?

一、工作原理

JavaScript到底是:

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

    Execution Context (执行上下文)中

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

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

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

二、实际工作过程分析

JavaScript 代码是如何执行的?

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

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

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

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

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

      • 一、创建:

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

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

        • 内存创建的第一阶段,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 就很好理解了。



Javascripts中if的优化
用户态的NFS Server
comments powered by Disqus