什么是JavaScript中的执行上下文和堆栈?

在这篇文章中,我将深入分析JavaScript中最基本的部分之一Execution Context 在这篇文章的最后,你应该更清楚地了解解释器试图做什么,为什么一些函数/变量可以在声明之前使用,以及它们的值如何确定。

什么是执行上下文?

当代码在JavaScript中运行时,执行它的环境非常重要,并评估为以下一项:

  • 全局代码 - 第一次执行代码的默认环境。
  • 功能代码 - 只要执行流程进入功能体。
  • 评估代码 - 要在内部评估函数中执行的文本。

您可以在线阅读大量参考资料,scope为了使本文更容易理解,让我们将该术语execution context看作当前代码正在评估的envionment /范围。现在,说够了,让我们看到,包括一个例子globalfunction / local上下文中计算的代码。

这里没有特别的事情发生,我们global context用紫色边框function contexts代表1个,绿色,蓝色和橙色边框代表3个不同的代表。只能有1个global context,可以从程序中的任何其他上下文访问。

您可以拥有任意数量的function contexts函数,并且每个函数调用都会创建一个新的上下文,该上下文创建一个专用作用域,其中在该函数内声明的任何内容都不能从当前函数作用域之外直接访问。在上面的示例中,函数可以访问在其当前上下文之外声明的变量,但外部上下文无法访问其中声明的变量/函数。为什么会发生?这个代码究竟是如何评估的?

执行上下文堆栈

浏览器中的JavaScript解释器被实现为单个线程。这实际上意味着在浏览器中一次只能发生一件事,其他行为或事件在所谓的“排队”中排队Execution Stack下图是单线程堆栈的抽象视图:

正如我们已经知道的,当浏览器第一次加载脚本时,它会global execution context默认进入如果在你的全局代码中你调用一个函数,你的程序的顺序流进入被调用的函数,创建一个新的execution context并将该上下文推送到该函数的顶部execution stack

如果您在当前函数内调用另一个函数,则会发生同样的情况。代码的执行流程进入内部函数,该内部函数创建一个execution context推送到现有堆栈顶部的新函数浏览器将始终执行execution context位于堆栈顶部的当前位置,一旦该函数完成执行当前操作execution context,它将从堆栈顶部弹出,并将控制返回到当前堆栈中的下面的上下文。下面的例子显示了递归函数和程序的execution stack

(function foo(i) {
    if (i === 3) {
        return;
    }
    else {
        foo(++i);
    }
}(0));

代码简单地调用自己3次,将i的值递增1.每次foo调用函数时,都会创建一个新的执行上下文。一旦上下文完成执行,它将弹出堆栈并且控制返回到它下面的上下文,直到global context再次到达。

有五点要记住的关键点execution stack

  • 单线程。
  • 同步执行。
  • 1全球背景。
  • 无限的功能上下文。
  • 每个函数调用都会创建一个新的execution context,即使是对自己的调用。

详细执行上下文

所以我们现在知道每次调用函数时execution context都会创建一个新函数但是,在JavaScript解释器中,每次调用execution context都有两个阶段:

  1. 创建阶段 [当函数被调用时,但在它执行任何代码之前]:
  • 创建范围链
  • 创建变量,函数和参数。
  • 确定的价值"this"
激活/代码执行阶段
  • 分配值,对函数的引用和解释/执行代码。

可以将每个execution context概念表示为具有3个属性的对象:

executionContextObj = {
    'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
    'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
    'this': {}
}

激活/变量对象[AO / VO]

executionContextObj是在函数被调用时,但实际函数执行之前创建的这就是所谓的第一阶段Creation Stage这里,解释器executionContextObj通过扫描传入的参数或参数的函数,局部函数声明和局部变量声明来创建函数。这次扫描的结果变成variableObjectexecutionContextObj

以下是解释器如何评估代码的伪概述

  1. 找到一些代码来调用一个函数。
  2. 在执行function代码之前,创建execution context
  3. 进入创建阶段:
  • 初始化Scope Chain
  • 创建variable object
    • 创建arguments object,检查参数的上下文,初始化名称和值并创建参考副本。
    • 扫描函数声明的上下文:
      • 对于找到的每个函数,variable object都要在该函数中创建一个属性,该函数名称具有指向内存中函数的引用指针。
      • 如果函数名已经存在,则参考指针值将被覆盖。
    • 扫描变量声明的上下文:
      • 对于找到的每个变量声明,请在variable object该变量名称中创建一个属性,并将该值初始化为undefined
      • 如果变量名已经存在,则variable object不做任何操作并继续扫描。
  • 确定"this"上下文中的值
激活/代码执行阶段:
  • 在上下文中运行/解释功能代码,并在代码逐行执行时分配变量值。

我们来看一个例子:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

在打电话时foo(22)creation stage外表如下:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

正如你所看到的,这些creation stage句柄定义了属性名称,除了形式参数/参数外,并没有给它们赋值。一旦creation stage完成,执行流程将进入函数,并且execution stage在函数完成执行后,激活/代码如下所示:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

关于吊装的一个词

你可以在网上找到许多hoisting在JavaScript中定义该术语的资源,并解释变量和函数声明被提升到它们的函数范围的顶部。然而,没有人详细解释为什么会发生这种情况,并且用你关于解释器如何创造的新知识来武装activation object,很容易看出其中的原因。以下面的代码示例:

(function() {

    console.log(typeof foo); // function pointer
    console.log(typeof bar); // undefined

    var foo = 'hello',
        bar = function() {
            return 'world';
        };

    function foo() {
        return 'hello';
    }

}());

我们现在可以回答的问题是:

  • 为什么我们可以在宣布之前访问foo?
    • 如果我们遵循creation stage,我们知道变量已经在之前创建activation / code execution stage所以当功能流程开始执行时,foo已经被定义在了activation object
  • Foo被宣布两次,为什么foo被证明是 function 和不是 undefined  string
    • 即使foo被声明了两次,我们也知道creation stage函数是在activation objectbefore变量上创建的,并且如果属性名已经存在activation object,我们可以绕过这个decleration。
    • 因此,function foo()首先创建一个引用activation object,当解释器到达时var foo,我们已经看到属性名称foo存在,所以代码什么都不做,并且继续。
  • 为什么是酒吧 undefined
    • bar实际上是一个具有函数分配的变量,我们知道这些变量是在这个变量中创建的,creation stage但是它们的初始值是undefined

概要

希望现在您已经掌握了JavaScript解释器如何评估您的代码。理解执行上下文和堆栈可以让你了解为什么你的代码正在评估不同的值,你最初并不期望的原因。

你是否认为解释器的内部工作是太多的开销或对JavaScript知识的必要性?知道执行上下文阶段是否可以帮助您编写更好的JavaScript?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值