定义函数表达式有两种方式:函数声明和函数表达式.
函数声明如下:
function functionName(arg0,arg1,arg2){ //函数体 }
首先是function关键字,然后是函数的名字.
FF,Safrai,Chrome和Opera都给函数定义了一个非标准的name属性,通过这个属性可以访问到函数指定的名字.这个函数的值永远等于跟在function关键字后面的标识符.
//只在FF,Safari,Chrome和Opera有效 alert(functionName.name)//functionName
函数声明的特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句后面.
sayHi(); function sayHi(){ alert("Hi!"); }
这种例子不会抛出错误,因为在代码执行之前会先读取函数声明.
第二种是函数表达式.
var functionName=function(arg0,arg0,arg2){ //函数体 }
这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName.这种情况下创建的函数叫做匿名函数(anonymous function),因为function关键字后面没有标识符.(匿名函数有时候也叫拉姆达函数.)匿名函数的name属性是空字符串.
函数表达式与其他表达式一样,在使用前必须先赋值.
以下代码会导致错误:
syaHi();//Uncaught ReferenceError: syaHi is not defined
var sayHi=function(){
alert("Hi!");
}
不要像下面这样写代码,这在ECMAScript中属于无效语法,JavaScript引擎会尝试修正错误,但不同浏览器修改不同.
//不要这样做 if(condition){ function sayHi(){ alert("Hi!"); } }else{ function sayHi(){ alert("Yo!"); } }
如果是使用函数表达式,就没什么问题了.
//可以这样做 var sayHi; if(condition){ sayHi=function(){ alert("Hi!"); } }else{ sayHi=function(){ alert("Yo!"); } }
能够创建函数再赋值给变量,也就能够把函数作为其它函数的值返回.
function creatComparisonFunction(propertyName){ return function(object1,object2){ var value1=object1[propertyName]; var value2=object2[propertyName]; if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } }; }
creatComparisonFunction()就返回了一个匿名函数.返回的函数可能会被赋值给一个变量,或者以其他方式被调用;不过,在creatComparisonFunction()函数内部,它是匿名的.在把函数当成值来使用的情况下,都可以使用匿名函数.
7.1 递归
递归函数是一个函数通过名字调用自己的情况下构造的.
function factorial(num){ if(num<=1){ return 1; }else{ return num*factorial(num-1); } }
上面是一个经典的递归阶乘函数.下面的代码却可能导致它出错.
var anotherFactorial=factorial; factorial=null; alert(anotherFactorial(4));//Uncaught TypeError: factorial is not a function
以上代码先把factorial()函数保存在变量anotherFactorial中,之后又将factorial变量设为null,结果指向原始引用只剩下一个.接下来调用anotherFactorial()时,由于必须执行factorial(),而factorial()已经不再是函数,所以会导致错误.
这种情况下,使用arguments.callee可以解决.
arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用.
function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } }
在编写递归函数时,使用arguments.callee总比使用函数名更保险,因为它可以确保无论怎么调用函数都不会出问题.
但在严格模式下,不能通过脚本访问arguments.callee.
不过可以使用函数表达式来达成相同的结果.
var factorial=(function f(num){ if(num<=1){ return 1; }else{ return num*f(num-1); } }); console.log(factorial(4));//24
7.2 闭包
闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数内部创建另一个函数.
function creatComparisonFunction(propertyName){ return function(object1,object2){ var value1=object1[propertyName]; var value2=object2[propertyName]; if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } }; }
加粗的两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量propertyName.即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量propertyName.之所以还能够访问这个变量,是因为内部函数的作用域链中包含creatComparisonFunction()的作用域.
当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链.然后,使用arguments和其他命名参数的值来初始化函数的活动对象(activation object).但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,....直至作为作用域链终点的全局执行环境.
在函数执行过程中,为读取和写入变量的值,就需要姑作用域链中查找变量.