javaScript中的词法范围/闭包

Ant*_*tiz 7 javascript closures lexical-closures

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

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

function f2() {
    return a;
}
f1(); // a is not defined
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

 var x = f(); 
 x();// "barb"
 console.log(b); //ReferenceError: b is not defined
Run Code Online (Sandbox Code Playgroud)

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

Aad*_*hah 47

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

函数在定义时不是在执行时创建它们的环境(范围)

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

什么是范围?

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

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

例如,考虑这个程序(它不是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
Run Code Online (Sandbox Code Playgroud)

在这里,我们创建了一个名为全局变量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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

此模式称为立即调用的函数表达式(IIFE).

词汇范围和动态范围

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

  1. 自由变量
  2. 绑定变量

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

词汇范围

在词法作用域中,自由变量必须属于父作用域.例如:

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
Run Code Online (Sandbox Code Playgroud)

与大多数编程语言一样,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
Run Code Online (Sandbox Code Playgroud)

而已.简单吧?

问题

您的第一个程序的问题是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)
Run Code Online (Sandbox Code Playgroud)

你的第二个程序是一个非常大的混乱:

function f() {
    var b = "barb";

    return function() {
        return b;
    }
}

console.log(b); //ReferenceError: b is not defined
Run Code Online (Sandbox Code Playgroud)

以下是错误:

  1. 你从未打过电话f.因此,b永远不会创建变量.
  2. 即使你调用f变量b也是本地的f.

这是你需要做的:

function f() {
    var b = "barb";

    return function() {
        return b;
    }
}

var x = f();

console.log(x());
Run Code Online (Sandbox Code Playgroud)

当你打电话给x它返回b.然而,这并不是b全球性的.为了实现b全球化,您需要这样做:

var x = f();
var b = x();
console.log(b);
Run Code Online (Sandbox Code Playgroud)

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

  • @Aadit M Shah:您的回答自相矛盾,充其量令人困惑。首先你说*“定义一个函数不会创建一个范围。调用一个函数会创建一个范围”*。美好的。然后在你的例子中,定义函数,然后调用,你为它们放置了相同的注释“创建范围”。这与您所做的“定义函数不会创建作用域”的声明相冲突。然后你在不引入它的情况下加入术语“调用范围”。它是调用函数的范围吗?你可能理解这个话题,但我会清理一下。 (2认同)

Poi*_*nty 5

你得到,"ReferenceError:b未定义",因为你的console.log()呼叫所在的地方没有定义"b" .这个功能里面有一个"b" ,但不在外面.你断言"b正在返回全球空间"是错误的.

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