先说结论: 第一段代码中 A = 1
试图修改自由变量 A
的值,但没有生效!
(function A() {
console.log(A); // [Function A]
A = 1;
console.log(window.A); // undefined
console.log(A); // [Function A]
}())
这是一个立即执行的函数表达式(Immediately-invoked function expression, IIFE),更特殊的,该函数表达式是一个具名函数表达式(Named function expression, NFE)。
NFE 有两个好玩的特性:
- 作为函数名的标识符(在这里是
A
)只能从函数体内部访问,在函数外部访问不到 (IE9+)。(kangax
有一篇 博客 详细讨论了 NFE 以及 IE6~8 的 JScript bug,推荐阅读!) ES5 Section
13 特别提及了这一点:
The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.
- 绑定为函数名的标识符(在这里是
A
)不能再绑定为其它值,即该标识符绑定是不可更改的(immutable),所以在 NFE 函数体内对A
重新赋值是无效的。ES5 Section 13 详细描述了创建 NFE 的机制:
The production FunctionExpression : function Identifier (
FormalParameterListopt ) { FunctionBody }
is evaluated as follows:
- Let funcEnv be the result of calling NewDeclarativeEnvironment passing the running execution context’s Lexical Environment as the argument
- Let envRec be funcEnv’s environment record.
- Call the CreateImmutableBinding concrete method of envRec passing the String value of Identifier as the argument.
- Let closure be the result of creating a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt and body specified by FunctionBody. Pass in funcEnv as the Scope. Pass in true as the Strict flag if the FunctionExpression is contained in strict code or if its FunctionBody is strict code.
- Call the InitializeImmutableBinding concrete method of envRec passing the String value of Identifier and closure as the arguments.
- Return closure.
注意步骤 3 和 5,分别调用了 createImmutableBinding 和 InitializeImmutableBinding 内部方法,创建的是不可更改的绑定。
要理解这两个特性,最重要的是搞清楚标识符 A
的绑定记录保存在哪里。让我们问自己几个问题:
标识符
A
与 该 NFE 是什么关系? 两层关系:首先,该 NFE 的name
属性是 字符串'A'
;更重要的是,A
是该 NFE 的一个自由变量。在函数体内部,我们引用了A
,但A
既不是该 NFE 的形参,又不是它的局部变量,那它不是自由变量是什么!解析自由变量,要从函数的 [[scope]] 内部属性所保存的词法环境 (Lexical Environment) 中查找变量的绑定记录。标识符
A
保存在全局执行环境(Global Execution Context)的词法环境(Lexical Environment)中吗? 答案是否。如果你仔细看过 ES5 Section 13 这一节,会发现创建 NFE 比创建 匿名函数表达式 (Anonymous Function Expression, AFE) 和 函数声明 (Function Declaration) 的过程要复杂得多:
对于 创建 AFE 和 FD, 步骤是这样:
The production
FunctionDeclaration : function Identifier ( FormalParameterListopt ) { FunctionBody }
is instantiated as follows during Declaration Binding instantiation (10.5):
Return the result of creating a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt, and body specified by FunctionBody. Pass in the VariableEnvironment of the running execution context as the Scope. Pass in true as the Strict flag if the FunctionDeclaration is contained in strict code or if its FunctionBody is strict code.
The production
FunctionExpression : function ( FormalParameterListopt ) { FunctionBody }
is evaluated as follows:
Return the result of creating a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt and body specified by FunctionBody. Pass in the LexicalEnvironment of the running execution context as the Scope. Pass in true as the Strict flag if the FunctionExpression is contained in strict code or if its FunctionBody is strict code.
比创建 NFE 简单多了有木有?那为么子创建 NFE 要搞得那么复杂呢?就是为了实现 NFE 的只能从函数内部访问A
,而不能从外部访问这一特性!咋实现的? 创建 NFE 时,创建了一个专门的词法环境用于保存 A
的绑定记录(见上面步骤 1~3)!对于 NFE, 有如下关系:
A.[[scope]] ---> Lexical Environment {'environment record': {A: function ...}, outer: --}----> Lexical Environment of Global Context {'environment record': {...}, outer --}---> null
可见,A
的绑定记录不在全局执行上下文的词法环境中,故不能从外部访问!