Javascript中如何声明和如何调用函数是一个令人厌烦的陈词滥调,但有些事情是这样的,你再说一遍我再说一遍。每次看到书上或者博客上写的Javascript函数调用有四种方式,就会想起孔乙己:分四种写法,你做到了吗?尽管存在所有缺陷,但Javascript令人着迷。JavaScript许多优雅特性的核心是作为一流对象的函数。函数被创建、分配给变量、作为参数传递、作为值返回,并像任何其他普通对象一样拥有属性和方法。Function作为一个关键对象,赋予了Javascript强大的函数式编程能力,同时也带来了不易控制的灵活性。1.函数声明变量声明首先创建一个匿名函数,然后将其赋值给一个指定的变量:varf=function(){//functionbody};通常我们不需要关心等号右边的表达式的作用域是全局的还是特定的在闭包中,因为它只能被等号左边的变量f引用,what应该关心的是变量f的范围。如果f对该函数的引用被销毁(f=null),并且该函数未分配给任何其他变量或对象属性,则匿名函数将被垃圾收集销毁,因为它丢失了所有引用。也可以使用函数表达式来创建函数:functionf(){//functionbody}与变量表达式不同的是,这种声明方式会为函数的一个内置属性名赋值。同时,将函数赋值给当前作用域内的一个同名变量。(函数的name属性,configurable,enumerable,writable都是false)functionf(){//functionbody}console.log(f.name);//"f"console.log(f);//f()Javascript变量有一个特点,就是会提前声明变量,表达式式的函数声明也会提前整个函数的定义,所以可以在函数定义前使用:console.log(f.name);//"f"console.log(f);//f()functionf(){//functionbody}函数表达式的声明会被提升到作用域的顶层,试试以下代码,不在本文重点:vara=0;console.log(a);//0ora()?functiona(){}Crockford建议始终使用第一种方式来声明函数。他认为第二种方式放宽了函数必须先声明再使用的要求,会导致混淆。(Crockford是类似于罗素的“尽心尽责的艺术家”比喻维特根斯坦的“尽心尽责的程序员”,这句话有点啰嗦)函数语句functionf(){}看起来像varf=functionf(){};的简写。vara=functionb(){};expression创建一个函数并将内置的name属性赋值给“b”,然后将这个函数赋值给变量a。你可以使用a()在外部调用它,但是你不能使用b(),因为函数已经赋值给了a,所以不会自动创建一个变量b,除非你用varb声明一个变量b=一个。当然这个函数的名字是“b”而不是“a”。使用Function构造函数也可以用来创建函数:varf=newFunction("a,b,c","re??turna+b+c;");该方法实际上在全局范围内生成了一个匿名函数,并将其赋值给变量f。2.递归调用递归用来简化很多问题,需要在函数体内调用自己://一个简单的阶乘函数varf=function(x){if(x===1){return1;}else{returnx*f(x-1);}};Javascript中函数的巨大灵活性使得在递归时很难使用函数名。对于上面的变量声明,f是一个变量,所以它的值很容易替换为:varfn=f;f=函数(){};函数是一个值,它被赋给了fn,我们期望用fn(5)计算一个值,但是因为函数内部仍然引用了变量f,所以它不起作用了。函数式声明看起来更好,但不幸的是:functionf(x){if(x===1){return1;}else{返回x*f(x-1);}}varfn=f;f=function(){};//maybeenwarningbybrowserfn(5);//NaN看来一旦我们定义了一个递归函数,就要注意不要轻易改变变量的名字。以上都是函数调用,还有其他调用函数的方法,比如调用对象方法。我们经常这样声明对象:varobj1={num:5,fac:function(x){//functionbody}};声明一个匿名函数并将其分配给对象的属性(fac)。如果我们想在这里写一个递归,我们将引用属性本身:varobj1={num:5,fac:function(x){if(x===1){return1;}else{returnx*obj1.fac(x-1);}}};当然也会遇到函数调用一样的问题:varobj2={fac:obj1.fac};obj1={};obj2.fac(5);//悲伤的方法是赋值给obj2的fac属性后,内部仍然引用了obj1.fac,所以……失败了。另一种方法会改进:varobj1={num:5,fac:function(x){if(x===1){return1;}else{returnx*this.fac(x-1);}}};varobj2={fac:obj1.fac};obj1={};obj2.fac(5);//ok通过this关键字获取函数执行时上下文中的属性,这样在obj2.fac执行时,函数内部就会引用obj2的fac属性。但是函数也可以通过任意修改上下文来调用,即调用即应用最好:obj3={};obj1。我们应该尝试解决这种问题,还记得前面提到的函数声明方式吗?vara=functionb(){};这种声明方法称为内联函数。虽然函数外没有声明变量b,但是在函数内部,b()可以调用自身,所以varfn=functionf(x){//tryifyoewrite"varf=0;"这里if(x===1){返回1;}else{返回x*f(x-1);}};varfn2=fn;fn=空;fn2(5);//OK//这里显示“varf=functionf(){}”和“functionf(){}”之间的区别varf=functionf(x){if(x===1){return1;}else{returnx*f(x-1);}};varfn2=f;f=空;fn2(5);//确定varobj1={num:5,fac:functionf(x){if(x===1){return1;}else{返回x*f(x-1);}}};varobj2={fac:obj1.fac};obj1={};obj2.fac(5);//好的varobj3={};obj1.fac。call(obj3,5);//ok这样,我们就有了一个内部可以使用的名字,不用担心递归函数赋值给谁,又是如何调用的。Javascript函数中的arguments对象有一个callee属性,它指向函数本身。所以也可以使用arguments.callee在内部调用函数:functionf(x){if(x===1){return1;}else{returnx*arguments.callee(x-1);但是arguments.callee是一个准备弃用的属性,并且可能会在未来的ECMAscript版本中消失,在ECMAscript5中“使用严格”时不能使用arguments.callee。***一个建议是:如果要声明递归函数,请慎用newFunction。Function构造函数创建的函数,每次调用都会重新编译一个函数,递归调用会造成性能问题——你会发现你的内存很快就会耗尽。原文链接:http://my.oschina.net/JackSparrow/blog/222221
