函数是搭建JavaScript的基本构建之一。一个函数就是一段JavaScript程序——包含用于执行某一任务或计算的一系列语句。要使用某一个函数,你必须在想要调用这个函数的执行域的某处定义它。
定义函数 - Defining function
函数声明 - Function declarations
一个函数定义(也称为函数声明,或函数语句)由一系列的函数关键字组成,依次为:
- 函数的名称
- 函数参数列表,包围在括号( )中并由逗号( , )区隔
- 函数功能,包围在花括号{ }中,用于定义函数功能的一些JavaScript语句
例如,以下的代码定义了一个简单的square函数:
function square(number){
return number * number;
}
函数square使用了一个参数,叫做number。这个函数只有一个语句,它说明该函数将函数的参数(即number)自乘后返回。函数的return语句确定了函数的返回值。
原始参数(比如一个具体的数字)被作为值传递给函数;值被传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。
如果你传递一个对象(即一个非原始值(non-primitive value),例如Array或用户自定义的对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下面的例子所示:
function myFunc(theObject){
theObject.name = "Toyota";
}
var mycar = {make:"Honda",model:"Accord",year:1998},
var x,y;
x = mycar.make; // x获取的值为"Honda"
myFunc(mycar);
y = mycar.make; // y获取的值为"Toyota"
// (make属性被函数改变了)
函数表达式 - Function expression
虽然上面的函数声明在语法上是一个语句,但函数也可以有函数表达式创建。这样的函数可以是匿名的;它不必有一个名称。例如,函数square也可以这样来定义:
var square = function(number){
return number * number
};
var x = square(4); // x得到的值为16
然而,函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中识别该函数:
var factorial = function fac(n){return n<2? 1:n*fac(n-1)};
console.log(factorial(3));
当将函数作为参数传递给另一个函数时,函数表达式很方便。下面的例子演示了一个叫map的函数如何被定义,而后使用一个表达式函数作为其第一个参数进行调用:
function map(f,a){
var result = []; // 创建一个新的数组
i;
for(i=0;i!=a.length;i++)
result[i] = f(a[i]);
return result;
}
下面的代码:
map(function(x){return x * x * x}, [0,1,2,5,10]);
返回[0,1,8,125,1000]。
在JavaScript中,可以根据条件来定义一个函数。比如下面的代码,当num等于0的时候才会定义myFunc:
var myFunc;
if(num == 0){
myFunc = function(theObject){
theObject.make = "Toyota"
}
}
除了上述的定义函数方法外,也可以在运行时用Function构造器由一个字符串来创建一个函数,很像eval( )函数。
当一个函数是一个对象的属性时,称之为方法,了解更多关于对象和方法的知识 使用对象
调用函数 - Calling functions
定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并确定函数被调用时该做些什么。调用函数才会以给定的参数真正执行这些动作。例如,一旦你定义了函数square,可以如下这样调用它:
square(s);
上述语句通过提供参数5来调用函数。函数执行完它的语句会返回值25.
函数一定要出于调用它们的域中,但是函数的声明可以被提升(出现在调用语句之后),如下例:
console.log(square(5));
/* ... */
function square(n){return n * n}
函数域是指函数声明时所在的地方,或者函数在顶层被声明时指整个程序。
注意:注意只有使用如上的语法形式(即如function funcName(){})才可以。而下面的代码是无效的。就是说,函数提升(function hoisting)仅适用于函数声明,而不适用于函数表达式。
console.log(square); // square is hoisted with an initial value undefined.
console.log(square(5)); // TypeError:square is not a function
var square = function(n){
return n * n;
}
函数地参数并不局限于字符串或数字。也可以将整个对象传递给函数。函数show_props(其定义参数 用对象编程)就是一个将对象作为参数的例子。
函数可以被递归,就是说函数可以调用其本身。例如,下面这个函数就是用递归计算阶乘:
function factorial(n){
if((n == 0) || (n == 1))
return 1;
else
return(n * factorial(n - 1));
}
可以计算1-5的阶乘如下:
var a,b,c,d,e;
a = factorial(1); // 1赋值给a
b = factorial(2); // 2赋值给b
c = factorial(3); // 6赋值给c
d = factorial(4); // 24赋值给d
e = factorial(5); // 120赋值给e
还有其它的方式来调用函数。常见的一些情形是某些地方需要动态调用函数,或者函数的实参数量是变化的,或者调用函数的上下文需要指定为在运行时确定的特定对象。显然,函数本身就是对象,因此这些对象也有方法(参考Function)。作为此中情形之一,apply( )方法可以实现这些目的。
函数作用域
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问其父函数中定义的所有变量和父函数有权访问的任何其他变量。
// 下面的变量定义在全局作用域(global scope)中
var num1 = 20,
num2 = 3,
name = "Chamahk";
// 本函数定义在全局作用域
function multiply(){
return num1 * num2;
}
multiply(); // 返回60
// 嵌套函数的例子
function getScore(){
var num1 = 2,
num2 = 3;
function add(){
return name + "scored" + (num1 + num2);
}
return add();
}
getScore(); // 返回"Chamahk scored 5"
作用域和函数堆栈 - Scope and Function stack
递归 - Recursion
一个函数可以指向并调用自身(call itself)。有三种方法可以达到这个目的:
- 函数名
- arguments.callee
- 作用域下的一个指向该函数的变量名
例如,如下的函数定义:
var foo = function bar(){
// statements go here
};
在这个函数体内,以下的语句是等价的:
- bar()
- arguement.callee()
- foo()
调用自身的函数我们称之为递归函数(recursive function)。在某种意义上,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。例如以下的循环:
var x = 0;
while(x < 10){ // "x < 10"是循环条件
// do stuff
x++;
}
可以被转化成一个递归函数和对其的调用:
function loop(x){
if(x >= 10) // "x >= 10"是退出条件(等同于"!(x<10)")
return;
// 做些什么
loop(x + 1); // 递归调用
}
loop(0);
不过,有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易很多:
function walkTree(node){
if(node == null)
return;
//do something with node
for(var i = 0;i < node.childNodes.length;i++){
walkTree(node.childNodes[i]);
}
}
跟循环函数相比,这里每个递归调用都产生了更多的递归。
将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。
这种类似堆栈的行为可以在下例中看到:
function foo(i){
if(i < 0)
return;
console.log('begin:' + i);
foo(i - 1);
console.log('end:' + i);
}
foo(3);
// 输出:
// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3
嵌套函数和闭包 - Nested functions and closures
可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包(closure)。
一个闭包是一个可以自己拥有相对独立的环境与变量的表达式(通常是函数)。
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以“继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
可以总结如下:
- 内部函数只可以在外部函数中