作用域、闭包、词法环境

作用域、闭包、词法环境

第一个问题:Scope, Closure, Lexical Environment三者分别是什么?

由于这个问题太难回答,暂时先不回答,只给出三者的中文翻译:

第二个问题:Scope, Closure, Lexical Environment三者的表现形式是怎样的?(我们怎样感受到它们的存在?)

Scope的存在

Closure的存在

function createPlus99() {
    var x = 55, y = 44;
    return function(z) {
        return x + y + z;
    }
}
var plus99 = createPlus99();
plus99(1); // 100

上面我自己写的一个例子展示了闭包的表现形式:当我们调用一个函数的时候,我们总是可以拿到这个函数定义时的函数体的外部的变量。(即使这些变量是在一个“外部函数”的函数体里声明,并且这个“外部函数”已经执行完了。)

Lexical Environment的存在

Lexical Environment是ECMAScript规定的JS引擎的一种内部机制,Scope和Closure就是因为这个机制而产生的,如果要说它有表现形式的话,Scope和Closure的表现形式就是它的表现形式。这样一来,开头提出的第一个问题“Scope, Closure, Lexical Environment三者分别是什么”就变成了1个问题:“Lexical Environment是什么?”

第三个问题: Lexical Environment(词法环境)是什么?

翻开神圣的ECMAScript2018, 我们可以看到这样的定义:

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment. Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

我的翻译:

词法环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构定义标识符与特定变量和函数的关联。词法环境由①环境记录(Environment Record)和②一个对外部词法环境(outer Lexical Environment)的可能空引用组成。通常,词法环境与ECMAScript代码的某些特定语法结构相关联,例如函数声明(FunctionDeclaration),代码块声明(BlockStatement)或Catch子句,并且每次评估(evaluated)此类代码时都会创建新的词法环境。

好的,我想大家看到这里都已经很清楚Lexical Environment是什么了:P

开个玩笑,继续看规范:

接着又有两段分别定义了词法环境的两个组成部分:环境记录(Environment Record)和对外部词法环境的引用(outer environment reference):

An Environment Record records the identifier bindings that are created within the scope of its associated Lexical Environment. It is referred to as the Lexical Environment's EnvironmentRecord.

The outer environment reference is used to model the logical nesting of Lexical Environment values. The outer reference of a (inner) Lexical Environment is a reference to the Lexical Environment that logically surrounds the inner Lexical Environment. An outer Lexical Environment may, of course, have its own outer Lexical Environment. A Lexical Environment may serve as the outer environment for multiple inner Lexical Environments. For example, if a FunctionDeclaration contains two nested FunctionDeclarations then the Lexical Environments of each of the nested functions will have as their outer Lexical Environment the Lexical Environment of the current evaluation of the surrounding function.

这两段我就不翻译了,我对Environment Record的理解就是执行一段会创建词法环境的代码时创建了一个词法环境,这个词法环境的Environment Record里保存了的那段代码里声明到当前词法环境的变量(变量名和对应值的),对outer environment reference的理解就是执行一段会创建词法环境的代码时创建了一个词法环境,这个词法环境的outer (lexical)environment reference是对那段代码外面最靠近的一个词法环境的引用。虽然读着很拗口,但我觉得我的解释真是通俗易懂,如果加上一个示例那就更好了,所以接下来我就写一个示例:

function outer() {
    var a = 99;
    // innerA
    {
        let a = 100;
        var c = 101;
        function innerA_A() {
            console.log(a, c, b);
            var a = 666;
        }
    }
    var b = 100;
    return innerA_A;
}
var innerA_A = outer(); 
innerA_A(); // "undefined"  101  100

要理解理解上面那段代码我们必须知道一个正确的常识以及改正一个错误的常识。

OK,让我们继续看上面的代码。执行上面的代码的过程中一共创建了几个词法环境,它们的环境记录和对外部词法环境的引用具体是怎样的? 我觉得应该是创建了3个词法环境:

  1. 调用outer时,执行outer的函数体,创建了一个词法环境,该词法环境的outer environment reference是对global environment(规范中有写)的引用;该词法环境的Environment Record最初的情况是:a->undefined, c->undefined, innerA_A->functioncode, b->undefined。 outer的函数体全部执行完成后,Environment Record变成了a->99, c->101, innerA_A->functioncode, b->100。

  2. 在执行outer函数体的过程中,我们遇到了一个代码块innerA,这里就会创建一个新的词法环境。该词法环境的outer environment reference是对outer函数的词法环境的引用;该词法环境的Environment Record最初的情况是空的,在该代码块执行完成之后,Environment Record变成了a->100,但是没有c->101,也没有innerA_A->functioncode,因为这两个变量都保存到了上面的那个词法环境中。只有那个用let声明的a,在不存在变量提升的情况下,保存到了该代码块创建的词法环境的环境记录中。所以用let声明变量最根本的意义在于,可以将变量保存到BlockStatement创建的词法环境的环境记录中,而不是必须保存到FunctionDeclaration创建的词法环境的环境记录。

  3. 在调用outer()返回的innterA_A时,开始执行innerA_A的函数体,创建了一个新的词法环境,该词法环境的outer environment reference是对代码块innerA的词法环境的引用;该词法环境的Environment Record最初的情况是a->undefined,函数体执行完成后Environment Record变成了a->666。

然而到现在我们还是不知道Lexical Environment是怎样带来Scope和Closure这两个东西的,但是离它们很近了,只需要一点Execution Context的知识就能解开谜题。