【问题标题】:Clarification on Javascript Execution Context关于 Javascript 执行上下文的说明
【发布时间】:2020-03-16 05:32:59
【问题描述】:

我一直在努力巩固我对 JS 执行上下文的理解,但无法解释为什么下面的代码没有打印出“hello world”。

var foo = "foo";

function test1() {
    console.log(foo)
    var bar = "hello world";
    test2();
}

function test2() {
    console.log(bar);
}

test1();

我(诚然非常不可靠)理解test2()test1() 内部执行,可以访问test1() 的执行上下文,并且应该能够通过向上移动来解析变量名称bar作用域链进入test1() 的执行上下文,其中定义了bar。相反,我在尝试打印 bar 时收到参考错误。

由于 JS 的词法作用域,我可以理解下面的代码是如何工作的,但我希望能解释一下 JS 在执行上下文和作用域链方面如何以不同的方式解释它们。

function test1() {
    console.log(foo)
    var bar = "hello world";
    function test2() {
        console.log(bar);
    }
    test2();
}

test1();

到目前为止,我自己寻找解释的最佳尝试是修改第一个代码块,如下所示:

var foo = "foo";
var bar = "not hello world :("

function test1() {
    console.log(foo)
    var bar = "hello world";
    test2();
}

function test2() {
    console.log(bar);
}

test1();

在这里,对test2() 的调用会打印出在全局范围内定义的“not hello world :(”。我的第一个想法是test2() 正在沿着范围链上升到test1 的执行上下文,全局执行上下文,但这似乎不对,因为它会在到达全局定义之前在test1()内部找到bar的定义.因此,我的第二个猜测是test2()的定义正在创建在定义全局执行上下文时进行类似“闭包”的捕获,并在 test1() 内调用它会为 bar 生成 null/undefined 的值,因为这是它在函数定义时的值(根本没有声明被提升)。因此,它不会向上移动到 test1() 的执行上下文中来搜索标识符。

任何解释/资源都会非常有帮助。我显然对这门语言很陌生,所以如果我的词汇/术语不太正确,请道歉。提前感谢您的帮助。

【问题讨论】:

  • test2() 在 test1() 内部执行,可以访问 test1() 的执行上下文——这是不正确的。范围由词法确定,意思是“由代码看起来像文本的样子”。 test2() 中的语句只能访问函数本身和包含它的函数的范围。
  • 感谢您的回复@Pointy。向我解释这个概念的视频是:youtube.com/watch?v=Nt-qa_LlUH0 使用相同的可视化工具 (tylermcginnis.com/javascript-visualizer) 和这段代码,Tyler 的可视化工具暗示 test2() 的执行上下文确实存在于 test1() 的内部。但我确定可视化器可能不正确?从词汇上讲,你能澄清一下“包含它”的确切含义吗?
  • 作者称之为“执行上下文”的动态概念和词法作用域的静态概念是有区别的。词法范围可以通过查看代码来确定,而无需运行它。你不知道代码运行时作用域内的变量会有什么值,但你可以通过从内向外工作来确定哪些变量在作用域内,哪些不在作用域内。
  • 如何根据您的定义确定函数参数(如回调等)内部声明的匿名函数的词法范围?
  • 同理。这都是关于从全局级别到函数体的{ } 嵌套。从函数向外工作以跟踪范围时,您可以通过嵌套的{ } 层“出去”,但不能“进入”。您通过向外跟踪遇到的{ } 块中声明的任何变量或函数都在范围内; inside 其他 { } 块的函数中的变量被隐藏。

标签: javascript closures executioncontext


【解决方案1】:

从哪里调用它和调用者的范围无关紧要。解析局部变量的关键在于它的声明位置。

test2() 无法访问bar,因为在声明函数时该范围内没有名为bar 的变量。事后无法操纵该本地范围。这是一成不变的。

这意味着函数可以访问本地范围内的变量,或者任何包含该函数声明的范围内的变量。这意味着遵循包含该函数声明的大括号 {} 一直回到文件的根目录。把它想象成一棵树:你的函数可以访问该函数和树根之间的任何变量。它不会访问该树的其他分支。


这是一件非常好的事情。它强制执行良好的封装。从调用者的角度来看,您希望您的函数是一个黑匣子。您希望函数中的局部变量是真正的局部变量。并且您想知道在您编写函数时函数可以访问哪些变量,而不是在运行函数时对弹出的值感到惊讶。 p>

【讨论】:

  • 感谢您的回复,亚历克斯。你说的很有道理。关于执行上下文的现成信息/文章肯定不能很好地传达这个概念,所以我感谢你的解释。您能否解释一下如何确定参数中声明的匿名函数的作用域(例如作为回调传递的匿名函数?)
  • 完全相同的规则适用于此。匿名函数也只能访问创建该函数的范围内的变量。
最近更新 更多