为何同作用域下IIFE无法访问let定义的变量b?
b会抛出ReferenceError? 这个问题的核心是**JavaScript自动分号插入(ASI)在悄悄改变代码的解析逻辑,再结合let的暂时性死区(TDZ)**特性,最终导致了报错。咱们一步步拆解来看:
第一段代码的实际解析逻辑
你写的第一段代码因为没有添加必要的分号,JavaScript引擎会按照ASI规则自动补充分号,但补的位置和你预期的完全不一样!
原代码:
function foo(){ var a = 1 let b = 2 (function bar() { console.log(a) console.log(b) }()) } console.log(foo());
JavaScript实际解析后的代码相当于:
function foo(){ var a = 1; // 注意这里!b的初始化被解析成了调用表达式 let b = 2(function bar() { console.log(a); console.log(b); }()); } console.log(foo());
看到问题了吗?let b = 2后面没有分号,引擎会把后面的(function bar(){...})()当成是对数字2的函数调用——也就是试图把IIFE的执行结果作为参数传给2(虽然数字不是函数,但这不是报错的直接原因)。
更关键的是:在执行2(...)的时候,会先调用内部的bar函数,而此时b还处于暂时性死区中——let声明的变量从作用域开始到完成初始化前,访问它都会抛出ReferenceError。这就是为什么console.log(b)会报错,而var a因为变量提升已经完成了初始化(赋值为1),所以console.log(a)能正常打印。
第二段代码为什么能正常运行?
当你把IIFE改成函数声明后,代码变成:
function foo(){ var a = 1 let b = 2 function bar() { console.log(a) console.log(b) } bar() } console.log(foo());
这里function bar()是一个独立的函数声明语句,JavaScript引擎会在let b = 2后面自动插入分号,解析后的代码符合你的预期:
function foo(){ var a = 1; let b = 2; function bar() { console.log(a); console.log(b); } bar(); } console.log(foo());
此时b已经完成初始化,bar作为foo的内部函数,能正常访问到foo作用域中的b,自然不会报错。
怎么修复第一段代码?
只需要在let b = 2后面添加分号,或者在IIFE的开头加分号,就能避免ASI的坑:
方案1:添加分号明确分隔语句
function foo(){ var a = 1; let b = 2; (function bar() { console.log(a); console.log(b); }()); } console.log(foo());
方案2:在IIFE前加分号(防止和前面的代码被误解析)
function foo(){ var a = 1 let b = 2 ;(function bar() { console.log(a) console.log(b) }()) } console.log(foo());
这样代码就能按照你的预期执行,正常打印1和2了。
内容的提问来源于stack exchange,提问作者shuizhongyuemin

