JavaScript中的函数
JavaScript中的函数使用function关键字,后跟一组参数以及函数体。例如:
function foo(arg0, arg1,...,arg) {
...
}
var foo = function () {
...
};
(function () {
...
})();
在JavaScript中有三种函数类型:函数声明,函数表达式和函数构造器创建的函数。下面分别介绍这三种函数类型。
函数声明
函数声明由一系列的function关键字组成,依次为:
- 有一个特定的名称
- 函数参数列表,包围在括号中并由逗号分隔
- 组成函数体的声明语句
例如:
function foo(arg0, arg1,...,arg) {
var a = 1;
console.log(a);
}
函数表达式
函数表达式和函数声明非常相似,依次为:
- 可选的名称(当省略函数名的时候,该函数就成为了匿名函数。)
- 函数参数列表,包围在括号中并由逗号分隔
- 组成函数体的声明语句
例如:
var foo = function () {
var b = 2;
console.log(b);
};
var foo = function bar() {
var b = 2;
console.log(b);
};
(function() {
var c = 3;
console.log(c);
})();
Function构造函数
可以通过Function对象使用new操作符创建一个构造函数。例如:
new Function('alert('hello'); alert('world');');
函数声明和函数表达式
这里重点学习函数声明和函数表达式。创建函数的最常用的两个方法是函数表达式和函数声明。
函数声明:
function 函数名称 (参数:可选){ 函数体 }
函数表达式:
function 函数名称(可选)(参数:可选){ 函数体 }
所以,创建一个函数如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?
区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。或者这么理解function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。
例如:
function foo(){} // 声明,因为它是程序的一部分
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
new function bar(){}; // 表达式,因为它是new表达式
(function(){
function bar(){} // 声明,因为它是函数体的一部分
})();
(function foo(){}); // 表达式 包含在分组操作符内
匿名和具名函数
对于函数表达式,最熟悉的场景就是回调,例如:
setTimeout(function(){
console.log("hello world!");
},1000);
这是一个匿名函数表达式,在日常开发和工具库中这种模式很常见。但是它也有几个缺点需要考虑。
- 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。
- 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑 自身。
- 匿名函数省略了对于代码可读性/可理解性很重要的函数名。一个描述性的名称可以让 代码不言自明。
例如:
具名函数
function foo(){
return bar();
}
function bar(){
return baz();
}
function baz(){
debugger;
}
foo(); //当调试器debugger的调用栈,栈中显示了baz,bar,foo。
// 这是一个自执行的函数,函数内部执行自身,递归
function self() {
self();
}
匿名函数
function foo(){
return bar();
}
var bar = (function(){
return function(){
return baz();
};
})();
function baz(){
debugger;
}
foo();//当调试器debugger的调用栈,栈中显示了baz,(anonymous),foo。
// 这是一个自执行的匿名函数,因为没有标示名称
// 必须使用arguments.callee属性来执行自己
var self = function () {
arguments.callee(); //在严格模式下,ES5禁止使用 arguments.callee()。
};
给函数表达式指定一个函数名,不会影响其功能,反而是一个比较好的实现方式,例如:
setTimeout(function handle(){
console.log("hello world!");
},1000);
立即执行函数表达式
(function foo() {
var a = 1;
console.log(1);
})();
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的
由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数,比如(function foo(){ .. })()。第一个 ( ) 将函数变成表 达式,第二个 ( ) 执行了这个函数。这种模式也叫立即执行函数表达式(IIFE)。
除了( ) 括号外,还有很多其他的方式也能让一个函数变成函数表达式,例如:
//&&,异或,逗号等操作符
true && function () { /* code */ } ();
1, function () { /* code */ } ();
//一元操作符号
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
但是依然使用( ) 括号,括号内部本来期望的就是函数表达式,主要是为了方便开发人员阅读。
IIFE一些常见应用,例如:
- 把它们当作函数调用并传递参数进去
var a = 1;
(function ( global ) {
var a = 2;
console.log( a ); // 2
console.log( global.a ); // 1
})( window );
console.log( a ); // 1
- 模块方法
var module = (function () {
var i = 0;
return {
get: function () {
return i;
},
set: function (val) {
i = val;
},
add: function () {
return ++i;
}
};
} ());
module.get(); // 0
module.set(1);
module.add(); // 1
module.i // undefined 因为i不是返回对象的属性
结尾
这一节是对学习JavaScript作用域中的函数作用域的一个补充。这样把琐碎的知识串联起来,更容易形成体系。