如下代码:
(function A() {
console.log(A); // [Function A]
A = 1;
console.log(window.A); // undefined
console.log(A); // [Function A]
}())
可以看到注释里的输出。
这也就是说A = 1这一步什么都没有发生。它既没有改变A的值,也没有在window中添加新的属性。
而如下代码:
(function A() {
console.log(A); // undefined
var A = 1;
console.log(window.A); // undefined
console.log(A); // 1
}())
这个还是很好理解的,var语句被hoist到函数顶端,函数内定义了变量A但是没有赋值,所以第一个log是undefined,因为有var,并没有向global添加属性,因此window.A也是undefined。
接着,
function A() {
console.log(A); // [Function A]
A = 1;
console.log(window.A); // 1
console.log(A); // 1
}
A();
不在立即执行这个函数,结果也很符合直觉,打第一个log时,函数作用域内没有找到A,因此向上层查找,找到函数A;然后A = 1使得window对象多了一个属性,第二个log结果是1,第三个log显然也是1。
那么问题来了,第一段代码里,A = 1到底做了什么呢?
先说结论: 第一段代码中
A = 1
试图修改自由变量A
的值,但没有生效!这是一个立即执行的函数表达式(Immediately-invoked function expression, IIFE),更特殊的,该函数表达式是一个具名函数表达式(Named function expression, NFE)。
NFE 有两个好玩的特性:
A
)只能从函数体内部访问,在函数外部访问不到 (IE9+)。(kangax有一篇 博客 详细讨论了 NFE 以及 IE6~8 的 JScript bug,推荐阅读!) ES5 Section
13 特别提及了这一点:
A
)不能再绑定为其它值,即该标识符绑定是不可更改的(immutable),所以在 NFE 函数体内对A
重新赋值是无效的。ES5 Section 13 详细描述了创建 NFE 的机制:注意步骤 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, 步骤是这样:
比创建 NFE 简单多了有木有?那为么子创建 NFE 要搞得那么复杂呢?就是为了实现 NFE 的只能从函数内部访问
A
,而不能从外部访问这一特性!咋实现的? 创建 NFE 时,创建了一个专门的词法环境用于保存A
的绑定记录(见上面步骤 1~3)!对于 NFE, 有如下关系:可见,
A
的绑定记录不在全局执行上下文的词法环境中,故不能从外部访问!