JavaScript中的词法作用域

7
我有点困惑 JavaScript 中作用域是如何工作的,主要是词法作用域。我知道全局作用域中的变量可以在任何地方访问,并且在 JavaScript 中创建新作用域的唯一方法是通过创建函数(或使用 ES6 中的 let)。然而,我并不真正理解什么是词法作用域。我在互联网上搜索了很久,但没有找到清晰的解释。
我觉得我开始有点理解了,但还是想向你们这些 JavaScript 高手确认一下是否正确。
据我理解,词法作用域指的是静态作用域,例如,函数的作用域不是由调用它的位置创建的,而是由函数本身被创建的位置确定的。下面的代码演示了这个概念:
var x = "global";

function foo() {
   console.log(x);
}

function bar() {
   var x = "bar";
   foo();
}

function baz() {
   var x = "baz";
   foo();
}


bar();
baz();

打印到控制台的内容是“全局”两次。这是因为当调用函数foo时,解释器首先检查foo的作用域是否有变量“x”,然后检查全局作用域,而不是bar或baz作用域。变量“x”被获取的位置不是函数foo被调用的位置,而是它被创建的位置,因此具有词法作用域。我的理解正确吗?这样说有意义吗?
另一个词法作用域的例子是闭包,对吗?例如,内部函数可以访问外部函数的变量,无论内部函数在何处被调用,这是因为具有词法作用域,正确吗?
最后,我的最后一个例子是箭头函数。它们允许“this”的词法作用域,对吗?例如,
   var obj = {
       name: "Rob",
       print() {
       setTimeout(() => {
         console.log(this.name)
       }, 1000);
     }
   };

因为箭头函数的词法作用域,而不是像标准内联函数那样将"this"绑定到全局对象,所以"this"被绑定到了obj。

我说的都对吗?还有,有没有人能给我一个清晰明了的词法作用域定义?在JavaScript中还有哪些其他词法作用域的例子值得我了解呢?

谢谢。


1
你第一个例子中的 foobarbaz 是闭包。你的想法基本上是正确的。 - Aluan Haddad
你可以在这里阅读更多相关信息。stackoverflow.com/questions/1047454/what-is-lexical-scope - Lalit
请详细说明它们为什么是闭包。闭包是指内部函数可以访问外部函数的变量,即使外部函数已经返回。但在foo、bar和baz的例子中并非如此。 - Anonymous
4个回答

5
为了理解词法作用域,您需要基本了解作用域。在JavaScript中,我们将作用域分类为三种类型:
1.函数作用域
2.块级作用域
3.词法作用域
函数作用域 -> 函数内定义的变量被视为函数作用域。在函数作用域中使用var关键字定义变量。
块级作用域 -> 在if、switch、for和while循环内部定义的变量。每当您看到“{}”花括号时,它就是一个块。在Es6中,const和let关键字允许开发人员在块级作用域中声明变量。这意味着这些变量仅存在于相应的块中。
   function animal(){
    if(true){
        var animal1 = "cat";
        const animal2 = "dog";
        let animal3 = "rat";
    }
    console.log(animal1);
    console,log(animal2); //animal2 is not defiend
    console,log(animal3); //animal3 is not defiend
}
animal();

结果

未定义animal2

未定义animal3

词法作用域 -> 一句话总结,“子作用域可以访问父作用域中的变量”。

var outerFunction = function()
{
    if(true){
        var x = 5;
        const y = 10;
    }

    var innerFunction = function(){
        if(true){
            alert(x);
            //alert(y);   //y is not defiend on line 13

        }
    }
    innerFunction();
}
 outerFunction();
 //console.log(x); // x is not defiend on line 20

变量的作用域是由其在源代码中的位置定义的。为了解析变量,javascript从最内部的范围开始向外搜索,直到找到它正在寻找的变量。词法作用域很好,因为我们可以通过查看代码很容易地确定变量的值;而在动态作用域中,变量的含义可能会在运行时更改,这会使其更加困难。

2
您对标准函数(包括嵌套函数)作用域的理解是正确的,但对于箭头函数,以下说法是错误的:
“由于箭头函数中'this'的词汇作用域,所以'this'绑定到obj。”
在箭头函数中,函数内部的this与创建它时外部的this相同。它不会像您的示例中那样绑定到obj,而是绑定到创建该obj的地方已经绑定的对象。
在以下情况下非常有用:
this.values.filter( x => x < this.max );

在箭头函数内,this与函数外部的this是相同的。如果使用普通函数,则可能会像这样编写:

this.values.filter( function ( x ) { return x < this.max }.bind( this ) );

或者:

var self = this;
this.values.filter( function ( x ) { return x < self.max } );

箭头函数的“this”解释太棒了。感谢您澄清这一点!那么,您说我的常规函数理解是正确的,但我的闭包理解是否正确? - Anonymous
@E.Sawyers 是的,我认为你对闭包的词法作用域的理解是正确的,但也许你对什么使一个函数成为闭包的理解有所偏差。任何使用非本地变量的函数都是闭包。按照这个定义,foo是一个闭包,因为它引用了x,而x不是该函数中的本地变量,但是barbaz不是闭包,因为它们只引用它们自己的本地变量x - Paul
非常感谢您的帮助! - Anonymous

1
当你查看程序的某些源代码时,你正在查看它的“词法结构”。当程序实际运行时,执行可以跳转和评估的内容可能会发生变化。这是所有开始变得有意义的部分。
你可能希望将其视为“函数词法作用域”和“块词法作用域”。对于其余部分,你似乎已经掌握了它的思想。

-1
“global”会被打印两次,因为“foo”是一个闭包,它已经将全局作用域的“x”添加到了它的内存中,这就是我所学到的,闭包是可以记住函数的函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接