这不是作用域问题,也不是闭包问题。问题在于声明和表达式之间的理解。
JavaScript代码自从Netscape的第一个版本以及Microsoft的第一份副本以来,都经过两个阶段的处理:
阶段1:编译 - 在此阶段,代码被编译成语法树(并且根据引擎可能还生成字节码或二进制码)。
阶段2:执行 - 然后解析编译后的代码。
函数声明的语法如下:
function name (arguments) {code}
参数是可选的(当然代码也是可选的,但那有什么意义呢?)。
但是JavaScript也允许你使用表达式来创建函数。函数表达式的语法与函数声明类似,只是它们在表达式上下文中编写。而表达式可以是:
=
符号右侧的任何内容(或对象文字上的:
)。
- 括号
()
中的任何内容。
- 函数参数(实际上已经被第2点覆盖了)。
表达式与声明不同,它们在执行阶段而不是编译阶段进行处理。因此,表达式的顺序很重要。
因此,为了澄清:
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();
第一阶段:编译。编译器发现变量someFunction
被定义,因此创建了它。默认情况下,所有创建的变量都具有未定义的值。请注意,在这个阶段编译器不能分配任何值,因为这些值可能需要解释器执行一些代码才能返回一个值来进行分配。而在这个阶段我们还没有执行代码。
第二阶段:执行。解释器看到您想将变量someFunction
传递给setTimeout。于是它这样做了。不幸的是,someFunction
当前的值是undefined。
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
阶段1:编译。编译器看到你正在声明一个名为someFunction的函数,所以它创建了该函数。
阶段2:解释器看到你想要将someFunction
传递给setTimeout。于是它这样做了。当前someFunction
的值是它的已编译函数声明。
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
阶段1:编译。编译器看到你已经声明了一个变量someFunction
,并创建它。与以前一样,它的值是未定义的。
阶段2:执行。解释器将一个匿名函数传递给setTimeout以便稍后执行。在这个函数中,它看到你正在使用变量someFunction
,因此它创建了一个闭包来引用该变量。此时,someFunction
的值仍然未定义。然后它看到你将一个函数分配给someFunction
。此时someFunction
的值不再是未定义的。1/100秒后,setTimeout触发,调用someFunction。由于其值不再是未定义的,所以它可以正常工作。
案例4实际上是案例2的另一个版本,并加入了一些案例3的元素。在将someFunction
传递给setTimeout时,由于它已经被声明,因此它已经存在了。
额外澄清:
你可能会想为什么setTimeout(someFunction, 10)
不会在本地副本和传递给setTimeout的副本之间创建闭包。答案是,在JavaScript中,如果数字或字符串为函数参数,则始终按值传递,对于其他所有内容则按引用传递。因此,setTimeout实际上并没有得到传递给它的变量someFunction(这将意味着创建一个闭包),而是只得到了someFunction所引用的对象(在这种情况下是一个函数)。这是JavaScript中在发明“let”关键字之前最广泛使用的打破闭包的机制(例如在循环中)。