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