JavaScript函数声明和求值顺序
为什么这些例子中的第一个不起作用,而其他所有的都起作用JavaScript函数声明和求值顺序,javascript,function-declaration,Javascript,Function Declaration,为什么这些例子中的第一个不起作用,而其他所有的都起作用 // 1 - does not work (function() { setTimeout(someFunction1, 10); var someFunction1 = function() { alert('here1'); }; })(); // 2 (function() { setTimeout(someFunction2, 10); function someFunction2() { alert('here2'); } })
// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();
// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();
// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();
// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();
因为在执行对
setTimeout()
的调用时,尚未分配someFunction1
someFunction3可能看起来类似,但在本例中,由于您将函数包装
someFunction3()
传递给setTimeout()
,因此对someFunction3()
的调用直到稍后才会计算。Javascript的作用域是基于函数的,而不是严格意义上的词法作用域。也就是说
- Somefunction1是从封闭函数的开始定义的,但是它的内容在赋值之前是未定义的
- 在第二个示例中,赋值是声明的一部分,因此它“移动”到顶部
- 在第三个示例中,当定义了匿名内部闭包时,变量就存在了,但直到10秒后才使用它,此时该值已被赋值
- 第四个例子既有第二个原因,也有第三个原因
符号右侧的任何内容(或对象文字上的=
):
- 括号中的任何内容
()
- 函数的参数(这实际上已经在第2部分中介绍) 表达式不同于声明是在执行阶段而不是编译阶段处理的。正因为如此,表达的顺序很重要 因此,澄清一下:
- 这既不是范围问题,也不是关闭问题。问题在于理解声明和表达式之间的关系
JavaScript代码,因为即使是Netscape的第一个JavaScript版本和Microsoft的第一个JavaScript副本,也会分两个阶段进行处理:
阶段1:编译-在此阶段,代码被编译成语法树(字节码或二进制代码取决于引擎)
阶段2:执行-然后解释解析的代码
函数声明的语法为:
function name (arguments) {code}
参数当然是可选的(代码也是可选的,但这有什么意义?)
但是JavaScript也允许您使用表达式创建函数。函数表达式的语法与函数声明类似,只是它们是在表达式上下文中编写的。表达方式如下:
第一阶段:汇编。编译器看到变量
someFunction
已定义,因此它将创建它。默认情况下,创建的所有变量的值均为undefined。请注意,此时编译器还不能赋值,因为这些值可能需要解释器执行一些代码来返回要赋值的值。在这个阶段,我们还没有执行代码
第二阶段:执行。解释器看到您想要将变量someFunction
传递给setTimeout。确实如此。不幸的是,someFunction
的当前值未定义
第一阶段:汇编。编译器看到您正在声明一个名为someFunction的函数,因此它创建了它 阶段2:解释器看到您想要将
someFunction
传递给setTimeout。确实如此。someFunction
的当前值是它的编译函数声明
第一阶段:汇编。编译器看到您声明了一个变量
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中最广泛使用的打破闭包(例如在循环中)的机制。这听起来像是遵循良好过程避免麻烦的基本情况。在使用变量和函数之前先声明它们,并像这样声明函数:
function name (arguments) {code}
避免使用var声明它们。这只是草率的,会导致问题。如果你养成了在使用前声明所有内容的习惯,那么你的大部分问题都会很快消失。在声明变量时,我会立即用有效值初始化它们,以确保它们都没有未定义。我还倾向于包含在函数使用全局变量之前检查其有效值的代码。这是防止错误的额外保护措施
所有这些工作原理的技术细节有点像你玩手榴弹的物理原理。我的简单建议是首先不要玩手榴弹
在代码开始时,一些简单的声明可能会解决大多数这类问题,但对co
// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();
// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();
function name (arguments) {code}