在javascript中,函数也是对象。它们只被定义一次,却可以被调用或执行任意次。
函数定义
定义函数有两种方式,一种是函数声明,另一种是函数表达式。
函数表达式
var sum=function(x,y){
return x+y;
};
实际上,函数表达式就是将一个匿名函数赋给一个变量。
函数表达式还可以在定以后立即被执行:
var sum=(function(x,y){
return x+y;
})(1,2);
这种函数又叫做即时函数。想要创建这种函数,只要像上面那样,将函数体包装在括号中,后面再跟一个括号表示执行该函数即可。后面的括号中的内容是参数列表。
函数声明语句
function sum(x,y){
return x+y;
}
两种方式的区别
函数声明语句会被提前到整个作用域的顶部,可以被在定义它之前的代码所调用;而函数表达式只有在定义它之后的代码中才能调用。
函数中的return语句
并不是每个函数都有return语句,return语句会作为函数的一个出口,停止执行函数并返回值。
- 如果函数有return语句,并且后面有一个表达式,则这个函数返回表达式的值;
- 如果函数有return语句,但是后面没有跟表达式,则返回undefined;
- 如果函数没有return语句,函数将执行完函数体内的所有语句,然后返回undefined。
函数调用
有四种方式可以来调用函数,分别是:
- 作为函数;
- 作为方法;
- 作为构造函数;
- 通过call()或apply()方法间接调用。
下面分别来说说,这四种方法分别是怎么调用的。
作为函数被调用
这种是最普通的一种方式。一个函数调用表达式是由函数对象加一对括号组成的。括号里面是参数列表,若没有参数,则为空。
//函数声明
function sum(x,y){
return x+y;
}
//调用函数
alert(sum(1,3)); //4
上面那个例子,alert()语句里面就是一个函数调用语句。
作为方法被调用
如果一个函数时一个对象的属性,那么它就是这个对象的方法。它将由该对象进行调用。前面在介绍对象的笔记中有提到过,访问对象属性是通过点(.)操作符来完成的。
function Person(name){
this.name=name;
}
Person.prototype.sayName=function(){
alert(this.name);
}
var p1=new Person("Tom");
//sayName作为p1的方法被调用
p1.sayName(); //Tom
作为构造函数被调用
通常,作为构造函数的函数、命名都以大写字母开头。它跟在new操作符后,用于创建对象。
var date=new Date();
Date就作为构造函数被调用,创建了一个日期对象。
间接调用
函数对象包含有两个方法,可以用来简介调用函数。这两个方法是call()和apply()。后面将具体介绍。
函数的形参和实参
函数的形参是指:定义函数时,指定的参数;而函数的实参是指:调用函数时,指定的参数。
- 当函数传入的实参个数比形参个数要少时,剩下的形参的值都将被设为undefined;
- 当函数传入的实参个数比形参个数要多的,没有办法直接获得未命名的参数的值,只能用arguments对象来指向实参对象,由此得到多出的参数的值。
arguments对象
每个函数都有一个arguments对象,指向实参对象的引用。arguments是一个类数组对象,它有length值,表示实参的个数。arguments[0]表示第一个参数,arguments[1]表示第二个参数,以此类推,arguments[length-1]表示最后一个实参。
function sum(){
var nums=0;
for(var i=0; i<arguments.length; i++){
nums+=arguments[i];
}
return nums;
}
alert(sum()); //0
alert(sum(1,3)); //4
alert(sum(1,2,3)); //6
callee
callee是arguments对象的一个属性,它是一个指针,指向拥有arguments对象的那个函数,即当前正在执行的函数。
可以利用这个函数实现对函数的递归调用。
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
//1*2*3
alert(factorial(3)); //6
var anotherFac=factorial;
alert(anotherFac(3)); //6
利用callee来实现递归函数,将避免函数名改变后出现不能调用的问题。
caller
caller也是一个指针,只不过它指向的是调用当前执行函数的那个函数(或环境),并且caller只在函数执行时才有定义。如果函数时由顶层调用的,那么caller包含的就是null。
它的用法是:functionName.caller
其中functionName是指当前所执行的函数。
function callerDemo(){
if(arguments.callee.caller){
var a=arguments.callee.caller.toString();
alert(a);
}else{
alert("This is a top function!");
}
}
function handleCaller(){
callerDemo();
}
handleCaller(); //function handleCaller(){callerDemo();}
上面这个例子,arguments.callee就是指当前执行的这个函数,所以arguments.callee.caller与callerDemo.caller是等价的。
callerDemo表示,如果它的caller存在,则返回它,如果不存在,则说明它是顶层函数,给出提示。
callerDemo这个函数嵌套在handleCaller里面,所以当handleCaller函数执行时,其内部执行了callerDemo,callerDemo的caller指针自然是指向handleCaller的。
我们可以再看看,不将callerDemo作为嵌套函数的结果:
function callerDemo(){
if(arguments.callee.caller){
alert(arguments.callee.caller);
}else{
alert("This is a top function!");
}
}
callerDemo(); //This is a top function!
这会儿,callerDemo是在顶层被调用的,所以,它的caller是null。函数内部定义,若它的caller是null,则给出提示"This is a top function!"。
注意:caller是非标准的,不是所有浏览器都支持。但是,绝大多数浏览器还是都实现了这个属性。
函数的属性、方法和构造函数
函数也有一些属性和方法。
name属性
name属性是非标准的,firefox、safari、chrome、opera都给函数定义了这个属性。通过这个属性可以访问到给定函数的名字。
function sum(x,y){
return x+y;
}
alert(sum.name); //sum
匿名函数的name属性值是空字符串。
length属性
函数也有length属性,这里的length属性是指函数的形参个数,即定义函数时,希望传入的参数的个数。
注意,这个length属性与arguments.length是不同的。arguments.length是表示实参个数。
function check(args){
var actural=args.length;
var expected=args.callee.length;
var txt="实参个数:"+actural+"; 形参个数:"+expected;
return txt;
}
function f(x,y,z){
return check(arguments);
}
alert(f(1,3)); //实参个数:2; 形参个数:3
call()和apply()
前面有提到过,可以用call()和apply()间接调用函数。这两个方法是属于函数的方法,它们的作用是一样的,主要用于改变作用域。
它们的第一个参数是,要调用的函数的母对象,即调用环境。在函数体内,用this来获得对它的引用。
它们还有可选参数:
对于call()方法来说,它要将该函数的所有参数一一作为自己的参数传入;
对于apply()方法来说,它只要将所有参数放入一个数组,然后再将该数组作为自己的参数即可。
function sum(x,y){
return x+y;
}
var o={x:1,y:2};
alert(sum.call(o,o.x,o.y)); //3
alert(sum.apply(o,[o.x,o.y])); //3
bind()
bind()方法是ECMAscript5中新增的方法,用于将函数绑定至某个对象上。其实与call()和apply()差不多。
当函数调用该方法时,会返回一个新的函数,这个新的函数会把调用这个方法的函数当做是传入的对象的方法来调用。
function sum(y){
return this.x+y;
}
var o ={x:1,y:3};
var ff=sum.bind(o);
alert(ff(1)); //2
构造函数
其实函数还可以通过Function构造函数来定义:
var f=new Function("x","y","return x+y");
Function()构造函数可以传入任意数量的字符串实参,最后一个参数表示函数体。
一般不使用这种方式来创建函数。