定义函数有函数声明和函数表达式两种方法:
1.函数声明:
function functionName(agr0,arg1,arg2){ //函数体 }
对于函数声明,它的重要特征是函数声明提升,意思是执行代码之前会先读取函数声明,这样就可以把函数声明放在调用语句的后面。
2.函数表达式:
var functionName=function(arg0,arg1,arg2){ //函数体 }
Firefox、Safari、Chrome、Opera都给函数定义了一个非标准的name属性,通过这个属性可以访问到函数指定的名字:functionName.name——“functionName”,匿名函数的name属性是null。
针对函数提升,我们在执行下面的代码时的结果会出现错误:
if(condition){ function sayHi(){ alert("Hi!"); } }else{ function sayHi(){ alert("Yo!"); } }
由于使用了函数声明,这样的话函数声明提升就会发生,无法得知先被定义的声明是哪一个,浏览器针对这个的错误修正是不一样的,但如果使用的是函数表达式就不同
var sayHi;
if(condition){ sayHi=function sayHi(){ alert("Hi!"); }; }else{ sayHi=function sayHi(){ alert("Yo!"); }; }
一、递归
递归函数是一个函数通过名字调用自身的情况下构成的
由于递归函数内部会重新调用本身,所以函数名会与函数体有较大的耦合,一旦函数名被改变,那么函数体内的函数名也必须更改,这样就会导致一定的不便。这种情况,一般可以通过用argument.callee(num-1);来代替函数名;也可以 利用函数表达式的方式:
var factorial=(funcion f(num){ if(num<=1){ return i; }else{ return num*f(num-1); } });
二、闭包
闭包是指有权访问另一个函数作用域中的变量的函数。要创建闭包,最常见的方式就是在函数内部创建另一个函数。
function createComparisonFunction(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。这是因为作用域链的关系,理解作用域链对理解闭包至关重要,当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象出于第三位,直到作为作用域链终点的全局执行环境。
在函数执行过程中,为读取和写入变量的值,就需要在作用域中查找变量。例如
function compare(value1,value2){ if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } } var result=compare(5,10);
以上代码先定义compare函数,然后又在全局作用域中调用了它。当调用compare时,会创建一个包含arguments、value1、value2的活动对象。全局执行环境的变量对象在compare()执行环境的作用域链中则处于第二位。(变量对象其实和活动对象类似,活动对象指的是参数,参数在函数中实际也是变量)
而对于闭包来讲,在另一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。因此,在createComparisonFunction的函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。另外,在createComparisonFunction()函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用链仍然在引用这个活动对象。也就是说,在这个函数返回了之后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,这个函数的活动对象才会被销毁。
var compareNames=createComparisonFunction("name"); var result=compareNames({name:"Nicholas"},{name:"Greg"}); compareNames=null;///解除对匿名函数的引用
(1)闭包与变量
因为闭包保存的是包含函数中的变量对象,所以闭包永远都只能取得变量对象中变量最终的值。
function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){ result[i]=function(){ return i; }; } return result; }
在上面这个例子中,每个函数实际上都会返回10,因为它们引用的都是同一个变量i
function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){ result[i]=function(num){ return function(){ return num; } }(i) }
return result; }
这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。由于函数参数按值传递,变量i的当前值复制给参数num。因此可以返回各自不同数值。
(2)this对象
匿名函数执行环境有全局性,因此其this对象通常指向window。
var name="the window"; var object={ name:"My Object"; getNameFunc:function(){ return function(){ return this.name; }; } }; console.log(object.getNameFunc(){});//"The Window"(在非严格模式下)
*每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量的时候,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。因此,我们通常可以把外部作用域中的this保存一个变量中,就可以在闭包中访问该对象了。
var name="This Window"; var object={ name:"My Object"; getNameFunc:function(){ var that=this; return function(){ return that.name; }; } }; console.log(object.getNameFunc(){});//"My Object"
三、内存泄漏
如果闭包的作用域中保存着一个HTML元素,那么就意味着该元素 无法被销毁。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此要正确回收HTML占用的内存除了将所需的元素保存在一个变量中,还要最后将其值设置为null。例如:
function assignHandler(){ var element=document.getElementById("someElement"); var id=element.id; element.onclick=function(){ console.log(id); } element=null; }
四、模仿块级作用域
js没有块级作用域,也就是说在一个for循环中定义的变量,是可以在同一函数作用域或是全局作用域中调用的。而且js从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见,但是如果后续声明中的变量有初始化,它则会执行该初始化。我们常常通过以下的语法来创建一个块级作用域(又称私有作用域):
(function(){ //这里是块级作用域 })();
*将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,函数表达式后跟括号,就意味着这个表达式中的函数会被立即执行,由于是匿名函数,在执行完毕之后,它将会被销毁,除非是使用在该私有作用域中定义的方法,否则,外部作用域是无法访问私有作用域的变量的。这种方法可以避免向全局作用域中添加过多的变量和函数。
五、私有变量
在私有作用域中定义的变量就是私有变量。要访问私有变量就要有特权方法,创建特权方法有两种方法。第一种是使用构造函数,在构造函数当中定义特权方法。
function MyObject(){ var privateVariable=10; funtion privateFunction(){ return false; } this.publicMethod=function(){ privateVariable++; return privateFunction(); }; }
我们之所以能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
静态私有变量
因为构造函数模式的缺点是针对每个实例都会创建一组新方法,所以我们一般会使用静态私有变量来实现特权方法,即在私有作用域中定义私有变量或函数。
(function (){ var privateVariable=10; function privateFunction(){ return false; } MyObject=function(){}; MyObject.prototype.publicMethod-function(){ privateVariable++; return privateFunction(); } })();
需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是一个函数表达式,函数声明只能创建局部函数,但那并不是我们想要的结果,处于同样的原因,我们也没有在声明MyObject时使用var关键字,记住:初始化未经声明的变量,总是会创建一个全局变量。因此,MyObject就成了一个全局变量,能够在私有作用域之外被访问到。使用这个方法,所有实例都会共享私有变量和函数。
模块模式
这里的模块模式指的是为单例创建私有变量和特权方法。单例指的就是只有一个实例的对象,js一般是以对象字面量来创建单例对象的。
var singleton={ name:value, method:function(){ //这里是方法的代码 } }
模块模式通过为单例添加私有变量和特权方法能够使其得到增强:
var singleton=function(){ //私有变量和私有函数 var privateVariable=10; function privateFunction(){ return false; } return { publicProperty:true; publicMethod:function(){ privateVariable++; return privateFunction(); } } }
*在单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况,可以在返回对象之前对其进行增强
var singleton=function(){ //私有变量和私有函数 var privateVariable=10; function privateFunction(){ return false; }
var object=new CustomType();
object.publicProperty=true;
object.publicMethod=function(){privateVariable++;return privateFunction();} return object; } }