ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明。对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略。 **函数声明的语法是这样的:** ~~~ <script> function functionName(){ 函数体 } </script> ~~~ 首先是function关键字,这个关键字表示我声明的是一个函数,或者说我将要声明一个函数了,告诉浏览器一下;然后是函数的名字,我们讲函数的名字一定要见名知义,接着里面是我们的函数体,也可以了解为函数表达式;在各大主流浏览器里面都给函数定义了一个非标准的name属性,通过这个属性可以访问到给函数指定的名字,这个属性的值永远等同于function后面的标识符; ~~~ function functionName(){ } alert(functionName.name); ~~~ 函数声明解析过程如下: 1\. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行环境中作用域链作为它的作用域。 2\. 为当前变量对象创建一个名为Identifier的属性,值为Result(1)。 **函数声明提升** 关于函数有一个重要的特性就是函数声明提升.意思是在读取代码前会先读取函数声明。这就意味着可以把函数声明放在调用它额语句后面;例如一下; ~~~ alert(functionName.name); function functionName(){ } ~~~ 这个例子不会抛出错误,因为在alert之前会先去读取这个函数声明; 函数表达式 第二种创建函数的方法是使用函数表达式: 函数表达式: (函数表达式分为匿名和具名函数表达式) function Identifier opt( FormalParameterList opt){ FunctionBody }  //这里是具名函数表达式 具名函数表达式的解析过程如下: 1\. 创建一个new Object对象 2\. 将Result(1)添加到作用域链的顶端 3\. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行的执行环境中作用域链作为它的作用域。 4\. 为Result(1)创建一个名为Identifier 的属性,其值为为Result(3),只读,不可删除 5\. 从作用域链中移除Result(1) 6\. 返回Result(3) 简单来说,ECMAScript是通过上下文来区分这两者的:假如 function foo(){} 是一个赋值表达式的一部分,则认为它是一个函数表达式。而如果 function foo(){} 被包含在一个函数体内,或者位于程序(的最上层)中,则将它作为一个函数声明来解析。显然,在省略标识符的情况下,“表达式” 也就只能是表达式了。 ~~~ function foo(){}; // 声明,因为它是程序的一部分 var bar = function foo(){}; // 表达式,因为它是赋值表达(AssignmentExpression)的一部分 new function bar(){}; // 表达式,因为它是New表达式(NewExpression)的一部分 (function(){ function bar(){}; // 声明,因为它是函数体(FunctionBody)的一部分 })(); ~~~ 还有一种情况: ~~~ (function foo(){})   这种情况也是函数表达式,它被包含在一对圆括号中的函数,在其上下文环境中,()构成了一个分组操作符,而分组操作符只能包含表达式,更多的例子: function foo(){}; // 函数声明 (function foo(){}); // 函数表达式:注意它被包含在分组操作符中 try { (var x = 5); // 分组操作符只能包含表达式,不能包含语句(这里的var就是语句) } catch(err) { // SyntaxError(因为“var x = 5”是一个语句,而不是表达式——对表达式求值必须返回值,但对语句求值则未必返回值。——译 } ~~~ 下面简单说说函数声明与函数表达式的异同。声明和表达式的行为存在着十分微妙而又十分重要的差别。 首先,函数声明会在任何表达式被解析和求值之前先行被解析和求值。即使声明位于源代码中的最后一行,它也会先于同一作用域中位于最前面的表达式被求值。 简单总结,区别在什么地方呢? 1\. 声明总是在作用域开始时先行解析;  2\. 表达式在遇到时候才运算。 函数声明还有另外一个重要的特点,即通过条件语句控制函数声明的行为并未标准化,因此不同环境下可能会得到不同的结果。即是:  // 千万不要这样做! // 不同[浏览器](http://www.2cto.com/os/liulanqi/)会有不同返回结果, ~~~ if (true) { function foo() { return 'first'; } } else { function foo() { return 'second'; } } foo(); ~~~ // 记住,这种情况下要使用函数表达式: ~~~ var foo; if (true) { foo = function() { return 'first'; }; } else { foo = function() { return 'second'; }; } foo(); ~~~ 那么,使用函数声明的实际规则到底是什么?  FunctionDeclaration(函数声明)只能出现在Program(程序)或FunctionBody(函数体)内。从句法上讲,它们 不能出现在Block(块)({ ... })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement(语句), 而不能包含FunctionDeclaration(函数声明)这样的SourceElement(源元素)。 另一方面,仔细看一看产生规则也会发现,唯一可能让Expression(表达式)出现在Block(块)中情形,就是让它作为ExpressionStatement(表达式语句)的一部分。但是,规范明确规定了ExpressionStatement(表达式语句)不能以关键字function开头。而这实际上就是说,FunctionExpression(函数表达式)同样也不能出现在Statement(语句)或Block(块)中(别忘了Block(块)就是由Statement(语句)构成的)。 由于存在上述限制,只要函数出现在块中(像上面例子中那样),实际上就应该将其看作一个语法错误,而不是什么函数声明或表达式。 那么我们应该在什么时候使用函数声明或函数表达式呢?函数声明只能出现在“程序代码”中,意味着只能在其它函数体中或者全局空间;它们的定义不能不能赋值给一个变量或属性,或者作为一个参数传递出现在函数调用中;下面的例子是函数声明的允许的用法,foo(),bar()和local()都是通过函数声明模式声明:  // 全局环境 ~~~ function foo() {} function local() { // 局部环境 function bar() {} return bar; } ~~~ 当你在语法上不能使用函数声明的时候,你就可以使用函数表达式。比如:传递一个函数作为参数或者在对象字面量中定义一个函数:  // 这是一个匿名函数表达式 ~~~ callMe(function () { //传递一个函数作为参数 }); ~~~ // **其他函数表达式** ~~~ var myobject = { say: function () { // I am a function expression } }; ~~~ 这种情况看起来好像是常规的变量赋值语句,即创建一个函数并将它复制给变量functionName.这种情况下创建的函数叫匿名函数。因为function关键字后面没有标识符。匿名函数的name属性是空字符串。函数表达式与其他表达式一样,在使用前必须先复制。而且函数表达式并不会函数生, 提升,即先执行函数表达式,再声明会报错; [JavaScript](http://lib.csdn.net/base/18 "JavaScript知识库")有很多有趣的用法,在[Google Code Search](http://www.google.com/codesearch?as_q=%22~function%22&as_lang=javascript)里能找到不少,举一个例子: ~~~ <script> ~function() { alert("hello, world."); }(); </script> ~~~ 试一下就知道这段代码的意思就是声明一个函数,然后立刻执行,因为Javascript中的变量作用域是基于函数的,所以这样可以避免变量污染,但这里的位运算符『~』乍一看让人摸不到头脑,如果去掉它再运行则会报错:SyntaxError。 为什么去掉位操作符『~』后运行会报错,这是因为从语法解析的角度看,Javascript不允许在函数声明的后面直接使用小括号,而函数表达式则没有这个限制,通过在函数声明前面加上一个『~』操作符,就可以让语法解析器把后面看成是函数表达式,同样的,在函数声明前面加上『!,+,-』等操作符也是可行的。 那我们为什么不使用下面这种函数表达式的方式呢? ~~~ <script> var foo = function() { alert("hello, world."); }(); </script> ~~~ 虽然从语法解析的角度看没有问题,但是上面的代码存在弊端,它引入了一个变量,可能会污染现有的运行环境,带来潜在的问题。 使用位操作符“~”的方法显得有点奇技淫巧,其实把函数声明用小括号套起来更易读: ~~~ <script> (function() { alert("hello, world."); })(); </script> ~~~