当我使用“传统”的C风格函数声明时,JavaScript中会发生什么?

9
我知道JavaScript中有几种定义函数的方式。其中最常见的两种是:
(1)  function add (a, b) {
         return a + b;
     }

(2)  var add = function (a, b) {
         return a + b;
     }

我很容易接受函数是一种可以像任何其他变量一样传递的对象的概念。所以我完全理解 (2) 的作用。它创建一个函数并将其分配给 add(假设这在全局范围内,因此 add 是一个全局变量)。但如果我使用 (1) 呢?执行顺序会有所不同:如果我使用 (1),那么我可以在代码中引用 add() 在定义 add() 的位置之前,但如果我使用 (2),则必须先将函数分配给 add,然后才能开始引用 add()

(1) 只是 (2) 的一种捷径,尽管它恰好像其他 C 风格语言一样允许我们在使用函数的地方 "下面" 定义函数。或者它在内部是一种不同类型的函数吗?哪一种更符合 JavaScript 的精神(如果这个术语不太模糊的话)?您是否会限制自己只使用其中一种,如果是,那么是哪一种?

2个回答

6

看起来你已经了解了函数声明1函数表达式(2)的主要特点。还要注意,在(1)中仍然有一个名为add的局部变量,其中包含一个函数值,就像在(2)中一样:

function hello () {
   alert('Hello World');
}

console.log(typeof hello);  // prints "function"

setTimeout(hello, 1000);    // you can still pass functions around as arguments,
                            // even when using function declarations.

值得一提的是,函数声明(1)不应该被用于有条件定义函数(比如在if语句中),因为正如你所提到的,它们会被JavaScript解释器自动移动到包含作用域的顶部2。这通常被称为hoisting

至于哪种方法更符合JavaScript的精神,我更喜欢使用函数表达式(2)。对于更权威的意见,Douglas Crockford在他流行的The Good Parts book书中的“坏部分”章节中列出了函数声明(1)2


1也被称为函数声明(请参见下面的@Tim Down评论)。
2实际上,一些浏览器可以处理if语句中的函数声明(再次参考下面的评论)。
3JavaScript: The Good Parts - 附录B:第113页。


2
根据ECMAScript规范,在“if”语句块内部的函数声明是语法错误。主要浏览器都不会抛出错误,可能是因为IE和Mozilla设置了先例。Mozilla通过包含ECMAScript的扩展FunctionStatement(http://www.jibbering.com/faq/#functionStatement, http://kangax.github.com/nfe/#function-statements)来解决这个问题。JScript对规范的尊重较少,内部执行神马就不得而知了。 - Tim Down
1
关于您的编辑的评论:函数声明也被称为函数语句,但这是不正确的。在我的先前评论中链接的comp.lang.javascript FAQ非常明确地指出了这一点:“术语函数语句已被广泛且错误地用来描述FunctionDeclaration。这是误导性的,因为在ECMAScript中,FunctionDeclaration不是Statement;程序中有一些允许Statement但不允许FunctionDeclaration的位置。” - Tim Down
@Tim:感谢您的有趣评论。我之前没有意识到“语句(statement)”命名问题。 - Daniel Vassallo

2
function foo() {};
foo.toString() //-> "function foo() {}"

var bar = foo;
bar.toString() //-> "function foo() {}"

这段代码声明了一个具名函数。无论哪个变量指向它,它的名称都会保留。另一种语法是匿名函数,它是没有命名的,只有在通过变量引用时才能访问。

var foo = function() {};
foo.toString() //-> "function () {}"

var bar = foo;
bar.toString() //-> "function () {}"

就使用哪种风格而言,没有什么主要的规则。虽然我个人更喜欢匿名语法,因为它提醒我函数实际上是可以传递的对象。我也倾向于采用“所有内容都在一个大型主对象中”的方法,这需要以此方式声明函数。

var MyThingy = {
  foo: function() { alert('foo') },
  bar: function() { MyThingy.foo() }
}

但是在函数被创建后,这些差异并不重要,它们的行为是相同的。但是匿名语法没有你提到的那种预扫描魔法,在复杂代码和奇怪的作用域情况下有更少的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接