一、 函数概述
1. 什么是函数
函数是一组可以随时随地运行的语句,是ECMAScript的核心。
函数是由这样的方式进行声明的:关键字 function、函数名、一组参数,以及置于括号中的待执行代码
函数的基本语法是这样的:
function sum(a,b)
{
returna + b;
}
其实通俗的说就是一个有名称的代码段,方便重用.
注意:
1)Javascript 的函数语法,因为Javascript本身就是区分大小写的,所以function不能写作Function或FUNCTION。
2)sum是函数的名称,这个并不是必须的,一会我会说到。
3)return是返回,如果不写的话,函数的返回是undefined.如果要返回多个值,可以返回个数组或者对象。
2. 函数的调用
函数可以通过其名字加上括号中的参数进行调用,
如 var result = sum(2,3);
3. 函数如何返回值
现有如下函数:
function sayHello(sName,sMessage){
alert(“Hello” + sName + sMessage);
}
可以发现,函数sayHello并未返回任何值,但是我们也不必要像在Java中使用void那样。而且就算函数确实有返回值,也没有必要明确的声明它。函数只需要在return运算符后面跟要返回的值即可。
另一个重要概念是,与在 Java 中一样,函数在执行过 return 语句后立即停止代码。因此,return 语句后的代码都不会被执行。
例如,在下面的代码中,alert窗口就不会显示出来。
function sum(num1,num2){
return num1 + num2;
alert(num1 + num2);
}
注意:就算函数没有明确的返回值,或者调用了没有参数的return语句,其实函数还是有返回的,返回的是undefined
二、 arguments对象
1. 概念说明
在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问他们。
例如在上面的sayHello()方法中,第一个参数是sName。用arguments[0]也可以访问这个值,即第一个值。(第一个参数位于0,第二个参数位于1,依次类推)。
functionsayHello(){
if(arguments[0] == “bye”){
return;
}
alert(arguments[0]);
}
2. 应用场景-检测参数个数
可以用 arguments 对象检测函数的参数个数,引用属性 arguments.length 即可
下面的代码将输出每次调用函数使用的参数个数:
functionhowManyArgs(){
alert(arguments.length);
}
howManyArgs(“string”,45);
howManyArgs();
howManyArgs(12);
上面的这段代码将一次显示:“2”,“0”,“1”。
注意:与其他程序设计语言不同,JavaScript不会验证传递给函数的参数个数是否等于函数定义的参数个数。开发者定义的函数都可以接受任意个数的参数(更具Netscape的文档,最多可以接受25个),而不会引发任何错误。任何遗漏的参数将会以undefined传递给函数,多余的参数将被忽略。
如 function sum(a,b){
return a + b;
}
1)参数少于实际定义参数的数目
var result =sum(1);
结果result为NaN,这就说明了参数b的值为undefined,但并不会报错,这就无形中造就了Bug。
2)参数多余实际定义参数的数目
sum(1,2,3,4,5);
结果为3,发现多余的参数被忽略了。
3)没有参数的函数
functionargs(){return arguments;}
可以发现,每个函数里都有一个默认的数组那就是arguments。也就是说函数就算没有写参数,依然可以给函数传入参数,并且对参数做相应运算,例如:
function args(){
varresult = 0;
for(i =0; i<arguments.length; i++){
result +=arguments[i];
}
return result;
}
调用它,args(1,2,3,4);结果为几个参数之和:10;
可以发现这样的求和函数,要比写死参数个数的要灵活的多了,扩展性也要好很多。我发现很多流行的开源框架如Jquery里,就有大量用到这样的方法。
3. 应用场景-模拟函数的重载
其实JavaScript函数本身是没有重载这个概念的,但是有时候我们往往需要函数重载这样的一个功能(尤其是在写一个写公共的js库方法时),所以可以通过arguments来模拟重载。
用 arguments 对象判断传递给函数的参数个数,即可模拟函数重载。如:
function show(){
if(arguments.length==1){
alert(arguments[0]);
}
if(arguments.length==2){
alert (arguments[0] +”,”+ arguments[1]);
}
}
show(1); ------------->1
show(1,2); ----------->1,2
这种模拟的重载,虽然不如重载那么好,但是也足以避开JavaScript的这种限制。 不过也可以看出,这是无法实现像java一样参数个数相同、类型不同的重载方式的。
三、 Function对象
1. 概念说明
JavaScript的函数实际上是功能完整的对象。
Function类可以表示开发者定义的任何函数。
用Function类直接创建函数的方法如下:
var function_name= new Function(arg1,arg2…,argN, function_body);
在上面的形式中,每一个arg是一个参数,最后一个参数是函数的主体(要执行的代码),这些参数必须是字符串。也就是说参数、要执行的代码也要是字符串的形式出现。
如:functionsayHi(sName,sMessage){
alert(“Hello”+sName+sMessage);
}
我们可以这样定义它,如下:
var sayHi = newFunction(“sName”,”sMessage”,”alert(\”Hello\”+sName+sMessage);”);
可以看出,由于字符串的关系,以这种方式来建立函数是相当的繁琐。
其实上面代码可以理解成一个指向函数的指针sayHi,那么我们再定义一个指向该函数的指针,如下
sayHi1 = sayHi;
syaHi =undefined;
sayHi1(“ly”,”hello”);
我们把sayHi指向的函数赋值给sayHi1指针,并删除sayHi,会发现并不影响sayHi1的调用,由此可见函数完全就是可以正常赋值传递的对象。
再看如下代码:
functiondoAdd(i){
alert(i+10);
}
functiondoAdd(i){
alert(i+20);
}
doAdd(10);
输出的结果是30。其实很好理解,相当于doAdd指针指向了一个新的Function对象罢了。
2. 函数是一个数据(Functions are data)
functionf(){return 1;}
var f = function(){return 1;}
两个函数的定义都是相同的。
alert(typeof f)
得到的结果是function,所以说JavaScript函数是个数据类型。
3. 匿名函数、回调函数
如:function(a){return a;}
匿名函数的作用有两个:
1)可以把匿名函数作为一个参数传入到另一个参数中(函数是一种数据类型,在js中完全你可以作为参数传入另一个函数中)
2)你可以理解运行这个匿名函数
var f = function(){return 1;}
function get(a){alert(a());}
get(f);
当然我们也可以使用匿名函数的方式。
get(function(){return 1;})
上例中,我们已经可以看到回调函数的身影。
回调函数定义为:传递一个函数A到函数B中,并且这个函数B执行函数A,我们把函数A叫做回调函数,如果没有名称,就叫做匿名回调函数。
回调函数作用:
1)当函数作为参数传递的时候,不用为这个函数定义一个名称,这样的好处是减少了全局变量。
2)节省了大量的代码
3)提高了程序的性能
4. 自调用函数(Self-invoking Functions)
(
function(){
alert(‘haha’);
}
)()
这种函数看起来很怪异,不过确实很简单。自调用函数方便使用,可以不用定义更多得全局变量。还有一个好处,就是这个函数不能被执行两遍。是非常适合初始化的工作。如Jquery里就用大量用到这样函数的代码。看一看JQ源码就知道,其实JQ就是一个完整的自调用函数。
(
function(window,undefined){
//在这里写入了所有JQ代码
}
)(window);
5. 内部函数(Inner Functions)
把函数作为值来思考一下,既然一个值可以定义在函数中,把函数作为数据放在函数中也未尝不可。
functiona(param){
function b(input){
return input*2;
}
return “The result is” + b(param);
}
var a =function(param){
var b = function(input){
return input*2;
};
return “The result is” + b(param);
}
b函数是在a函数之中的,这也就意味着,在a函数的外部是无法访问b函数的,所以b函数也可以叫做私有函数。
内部函数的好处还是很多的:
1)可以有更少的全局变量。过多的使用全局变量就可能由于命名冲突或者其他原因而产生过多的Bugs
2)因为是私有性,可以设计更好的接口函数供外部调用。
注意:var a = function(){}这种表达方式定义的函数在定义之前是无法调用的。如:
a();
var a =function(){};
这样是行不通的。
但是这样是可以的,如下:
a();
function a(){};[w1]
四、 闭包
在了解闭包之前,我觉得需要先看几个概念,不然不好懂。
1. 变量的作用域
如果函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖[w2] 。
var scope = “global”;
functioncheckscope(){
var scope = “local”;
return scope;
}
checkscope();-------------> local
scope-------------> global
在看下面一个列子,我们可以体会到变量声明时var的作用
var scope = “global”;
function checkscope2(){
scope = “local”;
return scope;
}
checkscope2();--------------->local
scope ----------------------->local
所以在声明局部变量的时候必须使用var语句,不然会改变同名全局变量的值
2. 函数的作用域和声明提前
js的函数作用域是指在函数内声明的所有变量在函数体内始终都是可见的[w3] 。在Js函数里声明的所有变量都被“提前”至函数体的顶部。
var scope = “global”;
function f(){
alert(scope); --------------->undefined
varscope = “local”;
alert(scope); -----------------> local
}
这个函数f()可以这样理解:
function f(){
varscope;
alert(scope);
scope= “local”;
alert(scope);
}
所以为了好理解,建议在函数里把需要声明的局部变量声明在函数的顶部。
3. 作用域链(先看下面的代码例子,方便理解)
这个概念比较抽象:每一段js代码(全局代码或者函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当JavaScript需要查找变量x的值的时候,他会从链表中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性,如果没有则会在链表中继续查找下一个,以此类推直到找到为止,如果最终都没找到将会爆出一个引用错误(ReferenceError)异常。
在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。但是在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象的创建规则是非常重要的。当定义一个函数时,它实际上保存一个作用域连。当调用这个函数时,它创建了一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上。
4. 闭包概念说明
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在作用域内,这种特性叫做闭包[w4] 。
例子:
var scope = “globalscope”;
functioncheckscope(){
var scope = “local scope”;
function f(){return scope;}
return f();
}
checkscope();------------------------->local scope;
改动:
var scope = “globalscope”;
functioncheckscope(){
var scope = “local scope”;
function f(){return scope;}
return f;
}
checkscope()();---------------------->local scope
这段代码中,checkscope()返回的是一个函数对象,而不是直接返回结果。
但是我们发现,结果依然没有改变。
javascript函数的执行用到了作用域链,这个作用域链是函数定义时创建的。嵌套函数f()定义在这个作用域里,其中的变量scope是局部变量,它绑定在了局部对象上,不管在何时何地执行函数f(),这种绑定在执行f()时依然有效。
简而言之,闭包的这个特性强大到令人吃惊:它们可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。