freeCodeCamp/guide/chinese/javascript/scopes/index.md

6.5 KiB
Raw Blame History

title localeTitle
Scopes 领域

如果你已经用JavaScript编程了一段时间那么毫无疑问你会遇到一个称为scope的概念。什么是scope ?你为什么要花时间学习它?

在程序员说话中, scope当前执行的上下文 。困惑?我们来看看以下代码:

var foo = 'Hi, I am foo!'; 
 
 var baz = function () { 
  var bar = 'Hi, I am bar too!'; 
    console.log(foo); 
 } 
 
 baz(); // Hi, I am foo! 
 console.log(bar); // ReferenceError... 

这是一个简单的例子但它很好地说明了所谓的_词法范围_ 。 JavaScript和几乎所有其他编程语言都有一个_词法范围_ 。还有另一种称为_动态范围的范围_ ,但我们不会讨论它。

现在术语_词汇范围_看起来很奇特但正如你将看到它原则上非常简单。在词法范围中有两种范围 全局范围_和_局部范围

在程序中键入第一行代码之前会为您创建一个_全局范围_ 。这包含您在程序中声明的所有函数之外的所有变量

在上面的示例中,变量foo位于程序的全局范围内,而变量bar在函数内声明,因此位于该函数的本地范围内

让我们逐行分解示例。虽然你可能会在这一点上感到困惑,但我保证你读完这篇文章时会有更好的理解。

在第1行我们声明变量foo 。这里没什么太花哨的。让我们称之为foo的左手大小LHS引用因为我们正在为foo分配一个值,它位于equal的左侧。

在第3行我们声明一个函数并将其赋值给变量baz 。这是对baz另一个LHS参考。我们正在为它赋值请记住函数也是值。然后在第8行调用此函数。这是一个RHS或者是对baz的右侧引用。我们正在检索baz的值,在这种情况下是一个函数然后调用它。如果我们将其值分配给另一个变量,例如foo = baz ,则另一个对baz RHS引用。这将是对foo的LHS参考和对baz的RHS参考。

LHS和RHS参考文献可能听起来令人困惑但它们对于讨论范围很重要。可以这样想LHS参考是为变量赋值而RHS参考则是检索变量的值。它们只是一种更简单更方便的方式来表示“检索价值”和“分配价值”。

现在让我们分析一下函数本身内部发生了什么。

当编译器在函数内编译代码时,它进入函数的本地范围

在第4行声明变量bar 。这是一个LHS参考bar 。在下一行,我们在console.log()有一个对foo的RHS引用。请记住我们正在检索foo的值,然后将其作为参数传递给方法console.log()

当我们有一个对foo的RHS引用时编译器会查找变量foo的声明。编译器在函数本身或函数的本地范围中找不到它,因此它上升到一个级别:到全局范围

此时您可能认为范围与变量有关。那是对的。范围可以被视为变量的容器。在本地范围内创建的所有变量只能在该本地范围内访问。但是,所有本地范围都可以访问全局范围。 (我知道你现在可能更加困惑,但只要跟我说几句话)。

因此编译器进入全局范围以查找变量foo的LHS引用。它在第1行找到一个所以它从LHS引用中检索值这是一个字符串 'Hi, I am foo!' 。此字符串将发送到console.log()方法,并输出到控制台。

编译器已经完成了函数内部的代码执行所以我们回到第9行。在第9行我们有一个变量bar的RHS参考。

现在, barbaz的本地范围内被声明但是在全球范围内有一个RHS参考bar 。由于全局范围内没有bar LHS引用因此编译器无法找到bar的值并抛出ReferenceError。

但是您可能会问如果函数可以在变量外部查看或者本地范围可以查看全局范围以查找LHS引用为什么全局范围不能窥探到本地范围那就是词汇范围的工作原理

... // global scope 
 var baz = function() { 
  ... // baz's scope 
 } 
 ... /// global scope 

这是与上面相同的代码,说明了范围。这形成了一种符合全球范围的层次结构:

baz -> global

因此,如果在baz范围内存在变量的RHS参考则可以通过全局范围内该变量的LHS参考来实现。但事实恰恰相反。

如果我们在baz内部有另一个功能怎么办?

... // global scope 
 var baz = function() { 
  ... // baz's scope 
 
  var bar = function() { 
     ... // bar's scope. 
  } 
 
 } 
 ... /// global scope 

在这种情况下,层次结构或范围链将如下所示:

bar -> baz -> global

内部的任何RHS引用bar的本地范围可以通过在全球范围内或LHS引用被fullfilled baz适适用范围“但在RHS参考baz的范围不能用在LHS参考fullfilled bar适适用范围”。

您只能遍历范围链,而不是向上遍历。

关于JavaScript范围您还应该了解其他两个重要事项。

  1. 范围由函数声明,而不是由块声明。
  2. 函数可以是前向引用的,变量不能。

观察(每个注释描述其写入的行的范围):

``` // outer在此范围内因为函数可以是前向引用的

function outer() { 
 
    // only inner() is in scope here 
    // because only functions are forward-referenced 
 
    var a = 1; 
 
    //now 'a' and inner() are in scope 
 
    function inner() { 
        var b = 2 
 
        if (a == 1) { 
            var c = 3; 
        } 
 
        // 'c' is still in scope because JavaScript doesn't care 
        // about the end of the 'if' block, only function inner() 
    } 
 
    // now b and c are out of scope 
    // a and inner() are still in scope 
 
 } 
 
 // here, only outer() is in scope 

```

参考

  1. Kyle Simpson的范围和闭包 。它详细介绍了作用域机制的工作原理并对JavaScript编译器的工作原理进行了表面描述因此如果您对此感兴趣请务必阅读它在GitHub上是免费的可以从O'Reilly购买。
  2. John Resig和Bear Bibeault 的JavaScript Ninja秘密 。这是一个更深入理解JavaScript的好指南。