JavaScript中定义函数最常用的方式是函数声明和函数表达式。这两种技术非常相似,有时甚至难以区分,但在后续章节中可以看到,它们之间还是存在着微妙的区别。
JavaScript定义函数最基本方式是函数声明,如下图:正如你所见,每个函数声明以强制性的function开头,其后紧接着强制性的函数名,以及括号和括号内一列以逗号分隔的可选参数名。函数体是一列可以为空的表达式,这些表达式必须包含在花括号内。除了这种形式以外,每个函数声明还必须包含一个条件:作为一个单独的JavaScript语句,函数声明必须独立(但也能够被包含在其他函数或代码块中)。
function myFunctionName(myFirstArg, mySecondArg) {
myStatement1;
myStatement2;
}
//在全局代码中定义samurai函数
function samurai() {
return 'samurai here';
}
//在全局代码中定义ninja函数
function ninja() {
//在ninja函数内定义hiddenNinja函数
function hiddenNinja() {
return 'ninja here';
}
return hiddenNinja();
}
如果你对函数式语言没有太多了解,仔细看看,你可能会发现你并不习惯这种使用方式:一个函数被定义在另一个函数之中!
//在全局代码中定义ninja函数
function ninja() {
//在ninja函数内定义hiddenNinja函数
function hiddenNinja() {
return 'ninja here';
}
return hiddenNinja();
}
在JavaScript中,这是一种非常通用的使用方式,这里用它作为例子是为了再次强调JavaScript中函数的重要性。
注意:让函数包含在一个函数中可能会因为忽略作用域的标识符解析而引发一些有趣的问题。
函数表达式
JavaScript中函数是第一类对象,除此以外也就意味着他们可以通过字面量创建,可以赋值给变量和属性,可以作为传递给其他函数的参数或函数的返回值。正因为函数有如此的基础结构,所以JavaScript能让我们把函数和其他表达式同等看待。例如,如下例子中我们可以使用数字字面量:
var a = 3;
myFunction(4);
同样,在相同位置可以用函数字面量:
var a = function () {
};
myFunction(function () {
});
这种总是其他表达式的一部分的函数(作为赋值表达式的右值,或者作为其他函数的参数)叫作函数表达式。函数表达式非常重要,在于它能准确地在我们需要使用的地方定义函数,这个过程让代码易于理解。
console.log('------------函数声明和函数表达式-----------------');
//独立的函数声明
function myFunctionDeclaration() {
//内部函数声明
function innerFunction() {
}
}
//函数表达式作为变量声明赋值语句中的一部分
var myFunc = function () {
};
//函数表达式作为一次函数调用中的参数
myFunc(function () {
//函数表达式作为函数返回值
return function () {
}
});
//作为函数调用的一部分,命令函数表达式会被立即调用
(function namedFunctionExpression() {
})();
//函数表达式可以作为一元操作符的参数立即调用
+function () {} ();
-function () {} ();
!function () {} ();
~function () {} ();
//示例代码的开头是标准函数声明,其包含一个内部函数声明:
function myFunctionDeclaration() {
//内部函数声明
function innerFunction() {
}
}
从这个示例中你能够看到,函数声明是如何作为JavaScript代码中的独立表达式的,但它能够包含在其他函数体内。与之比较的是函数表达式,它通常作为其他语句的一部分。它们被放在表达式级别,作为变量声明(或者赋值)的右值:
//函数表达式作为变量声明赋值语句中的一部分
var myFunc = function () {
};
或者作为另一个函数调用的参数或返回值。
//函数表达式作为一次函数调用中的参数
myFunc(function () {
//函数表达式作为函数返回值
return function () {
}
});
函数声明和函数表达式除了在代码中的位置不同以外,还有一个更重要的不同点是:对于函数声明来说,函数名是强制性的,而对于函数表达式来说,函数名则完全是可选的。
函数声明必须具有函数名是因为它们是独立语句。一个函数的基本要求是它应该能够被调用,所以它必须具有一种被引用的方式,于是唯一的方式就是通过它的名字。
从另一个方面来看,函数表达式也是其他JavaScript表达式的一部分,所以我们也就具有了调用它们的替代方案。例如,如果一个函数表达式被赋值给了一个变量,我们可以 用该变量来调用函数。
var doNothing = function () {};
doNothing();
或者,如果它是另外一个函数的参数,我们可以在该函数中通过相应的参数名来调用它。
function doSomething(action) {
action();
}
立即函数
函数表达式可以放在初看起来有些奇怪的位置上,例如通常认为是函数标识符的位置。接下来仔细看看这个构造。
当想进行函数调用时,我们需要使用能够求值得到函数的表达式,其后跟着一对函数调用括号,括号内包含参数。在最基本的函数调用中,我们要求把求值得到的函数的标识符作为左值。
不过用于被括号调用的表达式不必只是一个简单的标识符,它可以是任何能够求值得到函数的表达式。例如,指定一个求值得到函数的表达式的最简单方式是使用函数表达式。我们首先创建了一个函数,然后立即调用这个新创建的函数。
从右图中发现:我们首先创建了一个函数,然后立即调用这个新创建的函数。这种函数叫作立即调用函数表达式(IIFE),或者简写为立即函数。这一特性能够模拟JavaScript中的模块化,故可以说它是JavaScript开发中的重要概念。
加括号的函数表达式
还有一件可能困扰你的是上面例子中的我们立即调用的函数表达式:函数表达式被被包裹在一对括号内。为什么要这样做呢?其原因是纯语法层面的。JavaScript解析器必须能够轻易区分函数声明和函数表达式之间的区别。
如果去掉包裹函数表达式的括号,把立即调用作为一个独立语句function() {}(3),JavaScript开始解析时便会结束,因为这个独立语句以function开头,那么解析器会认为它在处理一个函数声明。每个函数声明必须有一个名字(然而这里并没有指定名字),所以程序执行到这里会报错。为了避免错误,函数表达式要放在括号内,为JavaScript解析器指明它正在处理一个函数表达式而不是语句。
还有一种相对简单的替代方案:(function(){}(3))也能够达到相同的目标(然而这种方案有些奇怪,故不经常使用)。把立即函数的定义和调用都放在括号内,同样可以为JavaScript解析器指明它正在处理函数表达式。
当然,最后4个表达式都是立即调用函数表达式主题的4个不同版本,在JavaScript库中会经常见到这几种形式:
//函数表达式可以作为一元操作符的参数立即调用
+function () {} ();
-function () {} ();
!function () {} ();
~function () {} ();
不同于用加括号的方式区分函数表达式和函数声明,这里我们使用一元操作符+、-、!和~。
这种做法也是用于向JavaScript引擎指明它们处理的是表达式,而不是语句。从计算机的角度来讲,注意应用一元操作符得到的结果没有存储到任何地方并不重要,只有调用IIFE才重要。现在我们已经学会了JavaScript中两种基本的函数定义方式(函数声明和函数表达式)的细节。
参考《JavaScript忍者秘籍》