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