javaScript中的词法范围/闭包

时间:2013-06-24 15:37:25

标签: javascript closures lexical-closures

我理解'js'中的函数有词法范围(即函数在定义它们时不创建它们的环境(范围)。)

function f1() {
    var a = 1;
    f2();
}

function f2() {
    return a;
}
f1(); // a is not defined

当我只运行'f()'时,它返回内部函数。我得到了,这就是“回归”的作用!

function f() {
    var b = "barb";
    return function() {
        return b;
    }
}
console.log(b); //ReferenceError: b is not defined

为什么会出现'ReferenceError:b未定义?' 但是上面的内部函数是不是可以访问它的空间,f()的空间等。因为'b'被返回到全局空间,console.log()不会工作吗?

但是当我将'f()'分配给一个新变量并运行它时:

 var x = f(); 
 x();// "barb"
 console.log(b); //ReferenceError: b is not defined

这会返回'b',即“barb”,但是当你再次运行console.log()时,你会得到'ReferenceError:'b'没有被定义';现在不是'b'在全球范围内,因为它已被退回?为什么'x()'也没有像'f()'那样返回内部函数?

4 个答案:

答案 0 :(得分:43)

你,我的朋友,非常困惑。你的第一个陈述本身是错误的:

  

函数在定义它们的时候创建它们的环境(范围)

实际上恰恰相反。定义函数不会创建范围。调用函数会创建一个范围。

范围是什么?

简而言之,范围是变量的生命周期。你看,每个变量都是天生的,生命和死亡。范围的开头标志着变量诞生的时间,范围的结束标志着变量的消亡时间。

一开始只有一个范围(称为程序范围或全局范围)。在此范围内创建的变量仅在程序结束时消失。它们被称为全局变量。

例如,考虑一下这个程序(它不是JavaScript):

x = 10       // global variable x

{            // beginning of a scope
    x = 20   // local variable x
    print(x) // 20
}            // end of the scope

print(x)     // 10

这里我们创建了一个名为x的全局变量。然后我们创建了一个块范围。在这个块范围内,我们创建了一个局部变量x。由于局部变量在我们打印x时会影响全局变量,因此我们得到20。当我们打印x时返回全局范围,我们得到10(本地x现已死亡。)

块范围和功能范围

现在编程中有两种主要的作用域 - 块作用域和函数作用域。

上一个示例中的范围是块范围。这只是一段代码。由此得名。块范围立即执行。

另一方面,函数作用域是块作用域的模板。顾名思义,函数作用域属于函数。但是,更准确地说,它属于函数调用。在调用函数之前,函数作用域不存在。例如:

var x = 10

function inc(x) {
    print(x + 1);
}

inc(3);   // 4
print(x); // 10
inc(7);   // 8

正如您在每次调用函数时都可以看到的那样,创建了一个新的范围。这就是您获得输出4108的原因。

JavaScript只有函数范围。它没有块范围。因此,如果要创建块作用域,则需要创建一个函数并立即执行它:

var x = 10;     // global variable x

(function () {  // beginning of a scope
    var x = 20; // local variable x
    print(x);   // 20
}());           // end of the scope

print(x);       // 10

此模式称为immediately invoked function expression (IIFE)

词汇范围和动态范围

函数作用域可以有两种类型 - 词法和动态。你看,在一个函数中有两种类型的变量:

  1. 自由变量
  2. 绑定变量
  3. 在范围内声明的变量绑定到该范围。未在范围内声明的变量是免费的。这些自由变量属于其他范围,但哪一个?

    词汇范围

    在词法范围中,自由变量必须属于父范围。例如:

    function add(x) {         // template of a new scope, x is bound in this scope
        return function (y) { // template of a new scope, x is free, y is bound
            return x + y;     // x resolves to the parent scope
        };
    }
    
    var add10 = add(10);      // create a new scope for x and return a function
    print(add10(20));         // create a new scope for y and return x + y
    

    与大多数编程语言一样,JavaScript具有词法范围。

    动态范围

    与词法作用域相反,在动态作用域中,自由变量必须属于调用作用域(调用函数的作用域)。例如(这也不是JS - 它没有动态范围):

    function add(y) {   // template of a new scope, y is bound, x is free
        return x + y;   // x resolves to the calling scope
    }
    
    function add10(y) { // template of a new scope, bind y
        var x = 10;     // bind x
        return add(y);  // add x and y
    }
    
    print(add10(20));   // calling add10 creates a new scope (the calling scope)
                        // the x in add resolves to 10 because the x in add10 is 10
    

    就是这样。简单吧?

    问题

    您的第一个程序的问题是JavaScript没有动态范围。它只有词汇范围。看到错误?

    function f1() {
        var a = 1;
        f2();
    }
    
    function f2() {
        return a;
    }
    
    f1(); // a is not defined (obviously - f2 can't access the `a` inside f1)
    

    你的第二个节目非常混乱:

    function f() {
        var b = "barb";
    
        return function() {
            return b;
        }
    }
    
    console.log(b); //ReferenceError: b is not defined
    

    以下是错误:

    1. 您从未致电f。因此,永远不会创建变量b
    2. 即使您致电f,变量b也会f为本地。
    3. 这是你需要做的:

      function f() {
          var b = "barb";
      
          return function() {
              return b;
          }
      }
      
      var x = f();
      
      console.log(x());
      

      当您致电x时,会返回b。然而,这并不会使b全球化。要使b全局化,您需要执行此操作:

      var x = f();
      var b = x();
      console.log(b);
      

      希望这有助于您了解范围和功能。

答案 1 :(得分:5)

你得到,“ReferenceError:b未定义”,因为“{”未定义console.log()调用的位置。 内部有一个“b”功能,但不在外面。你断言“b正在返回全球空间”是错误的。

当您调用“f()”函数返回的函数时,将返回该闭包变量“b”引用的值的副本。在这种情况下,“b”将始终是该字符串,因此该函数返回该字符串。它不会导致符号“b”成为全局变量。

答案 2 :(得分:2)

  

但是上面的内部函数不能访问它的空间,f()的空间等。

是的。它访问b变量,从函数返回其值。

  

因为'b'正在返回全球空间

没有。从函数返回值不是“在调用者范围中使变量可用”。调用函数(带f())是一个表达式,其结果是函数返回的值(在您的情况下,是未命名的函数对象)。然后可以在某处(x)分配该值,可以访问该属性,也可以将其丢弃。

然而,变量b在声明它的范围内保持私有。它不会在您调用console.log的范围内定义,这就是您收到错误的原因。

你想要的似乎是

var x = f();
var b = x(); // declare new variable b here, assign the returned value
console.log( b ); // logs "barb"

答案 3 :(得分:2)

    function f1() {
          var a = 1;
          f2();
          }

    function f2() {
       return a;
    }
    f1(); // a is not defined
  1. F2();不知道a,因为你从来没有把'a'传递给它,(那是范围是 在定义函数时创建).Look函数f2()本来可以访问 a如果它是在f1()中定义的; [函数可以访问相同范围内的变量 它们是“定义”而不是“被调用”]

    function f() {
       var b = "barb";
       return function(){
                         return b;
                        }
    }
    console.log(b); 
    
  2. 首先你需要调用f();执行f()后;它会返回另一个功能 需要执行。即

    var a=f();
    a();
    

    它会导致“barb”,在这种情况下,你返回的函数不是var b;

    function f() {
       var b = "barb";
       return b;
                 };
    
    console.log(f());
    

    这将在屏幕上打印倒钩