让我们直接进入函数声明和函数表达式,看看如何在JavaScript中使用这两种类型的函数。如果不仔细看,两者可能非常相似:
二者之间唯一的区别发生在赋值期间。解释器可以在语法解析期间访问到函数声明。而函数表达式是赋值表达式的一部分,在整个程序赋值完成前都不能执行。这点区别看起来很小,但是影响很大。考虑一下这种情况:
就像前面的例子里所看到的,函数表达式在被调用时抛出了异常,但函数声明执行正常。这个异常揭示了函数声明和函数表达式的关键不同。JavaScript可以理解声明的函数,并能在程序执行前将函数解析。因此,函数的调用在声明前后都没关系,因为JavaScript在幕后已经将函数提升到当前作用域的顶端。而函数表达式直到变量被赋值时才进行计算,因此当它被调用时仍是undefined。所以优秀的代码风格应该在当前作用域的顶端定义所有变量。这样做的话,脚本的顺序就跟JavaScript语法解析时的顺序就一样了。
前面省略了一个概念,当语法解析时,JavaScript会把所有函数声明移到当前作用域的顶端。所以声明式函数可以放在脚本的任意位置。为了更进一步探索声明和表达式之间的区别,看看这个例子:
你在看这段代码的时候可能会以为函数声明会崩溃,因为它跟函数表达式重名了。然而因为第二个函数是赋值变量的一部分,拥有自己的作用域,所以JavaScript把它们当成不同的实体。
下面的例子可能更让人困惑:
在前面的例子中,你已经看到了拥有相同名字的函数声明和函数表达式会被区别对待。在这个例子中,我会尝试基于程序运行的方式来按条件定义函数。看到这段脚本的控制流时,你可能会期待sayHey返回‘hey’,因为条件声明的执行结果为真。试试恰恰相反,返回的结果是‘no’,也就是说第二个版本的sayHey函数取代了第一个。更让人困惑的是sayHo函数的行为完全相反,这可以再次归结为编译时和运行时之间的差异。
你已经知道JavaScript会在解析脚本时把所有的函数声明放到当前作用域的顶端。在这个过程中,处在同一个作用域的第二个函数取代了第一个函数。这就是为什么会返回‘no’。你也知道函数表达式在赋值过程完成前都会被解析器忽略。赋值跟条件声明的计算一样在运行时发生。这就是为什么sayHo函数可以按条件定义。这里的关键是函数声明不能被条件式的定义。如果你需要条件定义,那就应该用函数表达式。此外,函数声明永远不应该放到控制流语句里,因为不同解释器的处理方式会不一样。