定义函数表达式有两种方式:函数声明和函数表达式。
函数声明如下:
函数名(arg0,arg1,arg 2){//函数体}首先是函数关键字,然后是函数名。
FF、Safari、Chrome和Opera都为函数定义了非标准的名称属性,通过该属性可以访问函数指定的名称。此函数的值始终等于函数关键字后面的标识符。
//仅在FF、Safari、Chrome和Opera alert中有效(函数名。name)//function name函数声明的特点是函数声明提升,这意味着在执行代码之前会读取函数声明。这意味着函数声明可以放在调用它的语句之后。
sayHi();函数sayHi(){ alert('Hi!');}此示例不会引发错误,因为在执行代码之前会读取函数声明。
二是功能表达。
var functionName=function (arg0,arg0,arg2) {//function body}的形式似乎是一个常规的变量赋值语句,即创建一个函数,并将其赋值给变量functionName。在这种情况下创建的函数称为匿名函数,因为函数关键字后面没有标识符。(匿名函数有时称为lambda函数。)匿名函数的name属性为空字符串。
像其他表达式一样,函数在使用前必须赋值。
以下代码导致错误:
syaHi();//未捕获的引用错误: syaHi未定义var sayHi=function(){ alert('Hi!');}不要这样写代码,这是ECMAScript中无效的语法。JavaScript引擎会尝试纠正错误,但是不同的浏览器会做不同的修改。
//如果(条件){function sayhi () {alert ('hi!')不要这样做。);} } else { function SayHi(){ alert(' Yo!');}}如果使用函数表达式,就没有问题。
//你可以这样做,var sayHiif(condition){ SayHi=function(){ alert(' Hi!');} } else { SayHi=function(){ alert(' Yo!');}}您可以创建一个函数并将其分配给一个变量,这样您就可以将该函数作为其他函数的值返回。
函数creatcomparionfunction(property name){ return function(object 1,object 2){ var value 1=object 1[property name];var value 2=object 2[PrOperty name];if(value 1 value 2){ return-1;} else if(value 1 value 2){ return 1;} else { return 0;} };} creatComparisonFunction()返回一个匿名函数。返回的函数可以赋给一个变量或以其他方式调用;但是,在creatComparisonFunction()函数内部,它是匿名的。当函数用作值时,可以使用匿名函数。
7.1递归
递归函数是在函数通过名称调用自身时构造的。
函数阶乘(num){ if(num=1){ return 1;} else { return num *阶乘(num-1);}}以上是一个经典的递归阶乘函数。以下代码可能会导致它出错。
var另一个阶乘=阶乘;阶乘=null警报(另一个阶乘(4));//unsighttypeerror : factorial不是函数上面的代码首先将factorial()函数保存在变量otherFactorial中,然后将factorial变量设置为null。因此,只剩下一个原始参考。当调用另一个阶乘()时,由于必须执行阶乘()并且阶乘()不再是函数,这将导致错误。
在这种情况下,可以通过使用参数来解决。
Arguments.callee是一个指向正在执行的函数的指针,因此它可以用来递归调用该函数。
函数阶乘(num){ if(num=1){ return 1;} else { return num * arguments . calleeper(num-1);}}在编写递归函数时,使用arguments.callee总是比使用函数名更安全,因为它可以确保无论如何调用函数都不会有问题。
但是,在严格模式下,不能通过脚本访问arguments.callee。
然而,函数表达式可以用来达到同样的结果。
var阶乘=(函数f(num){ if(num=1){ return 1;} else { return num * f(num-1);}});console.log(阶乘(4));//247.2关闭。
闭包指的是可以访问另一个函数范围内的变量的函数。创建闭包的一种常见方式是在函数中创建另一个函数。
函数creatcomparionfunction(property name){ return function(object 1,object 2){ var value 1=object 1[property name];var value 2=object 2[PrOperty name];if(value 1 value 2){ return-1;} else if(value 1 value 2){ return 1;} else { return 0;} };}这两行粗体代码是内部函数(匿名函数)中的代码,用于访问外部函数中的变量propertyName。即使内部函数被返回并在其他地方调用,它仍然可以访问变量propertyName。它之所以还能访问这个变量,是因为内部函数的作用域链包含creatComparisonFunction()的作用域。
调用函数时,将创建执行上下文和相应的范围链。然后,参数和其他命名参数的值将用于初始化函数的激活对象。但是,在范围链中,外部函数的活动对象总是排在第二位,外部函数的活动对象排在第三位.直到全局执行环境成为范围链的终点。
在函数执行过程中,为了读写变量值,需要在作用域链中寻找变量。
函数比较(值1,值2){ if(值1值2){ return-1;} else if(value 1 value 2){ return 1;} else { return 0;}} var result=compare (5,10)console . log(result)///-1上面的代码首先定义了compare()函数,然后在全局范围内调用。调用compare()时,将创建一个包含参数value1、value2和value2的活动对象。全局执行环境的变量对象(包括结果和比较)可以在。
后台的每个执行环境都有一个代表变量的对象——变量对象。全局环境中的变量对象始终存在,而局部环境中的变量对象(如compare()函数)仅在函数执行期间存在。创建compare()函数时,将会创建一个包含全局变量对象的范围链。此范围链存储在内部[[范围]]属性中。调用compare()函数时,将为该函数创建一个执行环境,然后通过复制该函数[[Scope]]属性中的对象来构建执行环境的范围链。之后,将创建另一个活动对象(此处用作变量对象),并将其推入执行环境的范围链的前端。
范围链本质上是指向变量对象的指针列表,它只引用变量对象,但实际上不包含变量对象。
每当在函数中访问变量时,将从作用域链中搜索具有相应名称的变量。一般来说,函数执行时会破坏局部活动对象,只有全局范围(全局执行环境的变量对象)保存在内存中。然而,关闭的情况不同。
在另一个函数中定义的函数会将包含该函数的活动对象(即外部函数)添加到其作用域中。
var compare=creatcomparionfunction(' name ');var result=coma pre({ name : ' Nicholas ' },{ name : ' Greg ' });下图显示了执行上述代码时包含函数和内部匿名函数的范围。
当createComparisonFunction()函数返回时,其执行环境的范围将被破坏,但其活动对象仍将保留在内存中;在匿名函数被销毁之前,createComparisonFunction()的活动对象将被销毁。
//创建函数varcompare=creat comparison function(' name ');//调用函数varresult=comalee({ name : ' Nicholas ' },{ name 3360 ' Greg ' });//取消引用匿名函数(以便释放内存)compareNames=null将compareNames设置为null以取消引用该函数意味着通知垃圾收集例程清除它。当匿名函数的作用域链被销毁时,其他作用域(除了全局作用域)可以被安全销毁。
因为闭包会承载包含它的函数的范围,所以它会比其他函数占用更多的内存。过度使用闭包可能会导致过多的内存占用,所以要谨慎使用闭包。
7.2.1闭包和变量
范围链的这种配置机制导致了一个明显的副作用,那就是闭包只能得到函数中任意变量的最后一个值。
闭包中保存的是整个变量对象,而不是一个特殊的变量。
函数createFunctions(){ var result=new Array();for(var I=0;i10i ){结果[I]=函数(){返回I;};}返回结果;}在上面的代码中,这个函数将返回一个函数数组。从表面上看,似乎每个函数都应该返回自己的索引值,但实际上,每个函数都返回10。因为createfunctions()函数的活动对象存储在每个函数的作用域链中,所以它们都引用同一个变量I,当createFunctions()函数返回时,变量I的值为10,然后每个函数都引用存储变量I的同一个变量对象。
但是,我们可以通过创建另一个匿名函数来强制闭包按预期运行。
函数createFunctions(){ var result=new Array();for(var I=0;i10I){ result[I]=function(num){ return function(){ return num;} }(I);}返回结果;}重写后,每个函数将返回自己不同的索引值。在这个版本中,我们没有将闭包直接分配给数组,而是定义了一个匿名函数,并将执行匿名函数的结果立即分配给数组。这里的匿名函数有一个参数num,它是最终函数返回的值。在调用每个匿名函数的时候,我们传入了变量I,因为函数参数是按值传递的,所以,变量I的当前值会被复制到参数num中。在这个匿名函数中,创建并返回一个用于访问num的闭包。因此,结果数组中的每个函数都有自己num变量的副本,因此它可以返回自己不同的数值。
7.2.2关于这个对象。
该对象在运行时绑定在基本函数的执行环境中。在全局函数中,这等于window,而当函数作为对象的方法调用时,这等于该对象。然而,匿名函数的执行环境是全局的,所以它的这个对象通常指向窗口。
var name=“窗口”;var object={ name:'my object ',getname func : function(){ return func(){ return this . name;};}};alert(object . GetNameFunc());window(在非严格模式下)每个函数在被调用时都会自动获取两个特殊变量,this和arguments。当内部函数搜索这两个变量时,它只会搜索它的活动对象,所以永远不可能在外部函数中直接访问这两个变量。
但是,通过将此对象保存在可由闭包访问的变量的外部范围中,闭包可以访问该对象。
var name=“窗口”;var object={ name:'my object ',getnameffunc : function(){ var that=this;return function(){ return . name;};}};alert(object . GetNameFunc());//我的对象在定义匿名函数之前,我们将这个对象分配给了一个名为。在定义闭包之后,闭包也可以访问这个变量,因为它是在包含函数中专门声明的变量。即使在函数返回之后,它仍然引用对象,所以调用object.getNameFunc()()会返回我的对象。
注意:这个和参数也有同样的问题。如果要访问作用域中的arguments对象,必须将对该对象的引用保存在另一个闭包可以访问的变量中。
var name=“窗口”;var object={ name:'my object ',getname : function(){ return this . name;}};console . log(object . getname());//my object console . log((object . getname)());//my object console . log((object . getname=object . getname)());window代码的最后一行首先执行赋值语句,然后在赋值后调用结果。因为这个赋值表达式的值是函数本身,所以这个的值不能被维护,结果返回‘this window’。
7.2.3内存泄漏。
因为IE9之前的版本对JScript对象和COM对象使用了不同的垃圾收集例程,所以闭包会在IE的这些版本中造成一些特殊的问题。具体来说,如果一个HTML元素存储在闭包的范围链中,这意味着该元素不能被销毁。
函数assignHandlet(){ var element=document . getelementbyid(' somelement ');element . onclick=function(){ alert(element . id);};}上面的代码创建了一个闭包作为元素的事件处理程序,该事件处理程序又创建了一个循环引用。由于匿名函数保存了对assignHandler()活动对象的引用,因此不能减少对元素的引用数量。只要匿名函数存在,对元素的引用数量至少为1,因此它占用的内存永远不会被回收。但是,这个问题可以通过稍微重写代码来解决。
函数assignHandlet(){ var element=document . getelementbyid(' somelement ');var id=element.idelement . onclick=function(){ alert(id);};元素=null}在上面的代码中,通过在变量中保存element.id的副本并在闭包中引用该变量来消除循环引用。
我们善意地提醒大家,闭包将引用包含函数的整个活动对象,该对象包含元素。即使闭包没有直接引用元素,包含函数的活动对象仍然会保持引用。因此,有必要将元素变量设置为null。这样就可以取消对DOM对象的引用,顺利减少引用次数,正常恢复其占用的内存。
下面介绍一下函数表达式。
在JavaScript编程中,函数表达式是一项非常有用的技术。使用函数表达式可以实现动态编程,无需命名函数。匿名函数,也称为lambda函数,是使用JavaScript函数的一种强大方式。下面总结了函数表达式的特点。
表达式不同于函数声明。函数声明需要名称,但函数表达式不需要。没有名称的函数表达式也称为匿名函数。
当不确定如何引用函数时,递归函数变得更加复杂。递归函数应该总是使用参数,被调用方递归调用自己,不要使用函数名——。函数名可能会改变。
当在函数内部定义了其他函数时,就创建了闭包。闭包可以访问包含函数中的所有变量。原理如下。
在后台执行环境中,闭包的范围链包含自己的范围、包含函数的范围和全局范围。通常,函数的作用域及其所有变量在函数执行后都会被破坏。
但是,当函数返回闭包时,函数的作用域将保留在内存中,直到闭包不存在。
使用闭包可以模仿JavaScript中的块级作用域(JavaScript本身没有块级作用域的概念)。要点如下。
立即创建并调用一个函数,这样就可以执行其中的代码,而不会在内存中留下对该函数的引用。因此,函数内部的所有变量都将被立即销毁,除非某些变量被分配给包含范围(即外部范围)内的变量。闭包也可以用来在对象中创建私有变量。相关概念和要点如下。即使在JavaScript中没有私有对象属性的正式概念,公共方法也可以通过闭包实现,包含范围中定义的变量可以通过公共方法访问。可以访问私有变量的公共方法称为特权方法。
可以使用构造函数模式和原型模式实现自定义类型的特权方法,也可以使用模块模式和增强模块模式实现单实例的特权方法。
JavaScript中的函数表达式和闭包是非常有用的特性,很多函数都可以通过它们来实现。然而,因为必须维护额外的作用域来创建闭包,过度使用它们可能会占用大量内存。