在JavaScript的立即执行的具名函数A内修改A的值时到底发生了什么?
如下代码:
(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到底做了什么呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
先说结论: 第一段代码中
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
的绑定记录不在全局执行上下文的词法环境中,故不能从外部访问!找到了,在http://www.ecma-international.org/ecma-262/5.1/#sec-13,关于具名表达式的一段:
上面第3和5条提到的CreateImmutableBinding和InitializeImmutableBinding两个方法(见Declarative Environment Records)会将具名函数的名称绑定到相应的函数对象(见Creating Function Objects)上,这个绑定是不可变的。
因此在函数表达式内可以通过函数名读取函数本身,但是无法改变函数名的引用。
由于JavaScript作用域,你的这个立即执行的函数表达式A被封在了内部。
我尝试着修改A的引用,但是失败了:
强行记忆一波:在自执行的具名函数中,修改函数引用无效。哈哈哈...