亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

javascript高級編程之函數表達式 遞歸和閉包函數

2019-11-20 11:08:09
字體:
來源:轉載
供稿:網友

定義函數表達式有兩種方式:函數聲明和函數表達式.

函數聲明如下:

function functionName(arg0,arg1,arg2){ //函數體}

首先是function關鍵字,然后是函數的名字.

FF,Safrai,Chrome和Opera都給函數定義了一個非標準的name屬性,通過這個屬性可以訪問到函數指定的名字.這個函數的值永遠等于跟在function關鍵字后面的標識符.

//只在FF,Safari,Chrome和Opera有效alert(functionName.name)//functionName

函數聲明的特征就是函數聲明提升(function declaration hoisting),意思是在執行代碼之前會先讀取函數聲明.這就意味著可以把函數聲明放在調用它的語句后面.

sayHi();function sayHi(){ alert("Hi!");}

這種例子不會拋出錯誤,因為在代碼執行之前會先讀取函數聲明.

第二種是函數表達式.

var functionName=function(arg0,arg0,arg2){ //函數體}

這種形式看起來好像是常規的變量賦值語句,即創建一個函數并將它賦值給變量functionName.這種情況下創建的函數叫做匿名函數(anonymous function),因為function關鍵字后面沒有標識符.(匿名函數有時候也叫拉姆達函數.)匿名函數的name屬性是空字符串.

函數表達式與其他表達式一樣,在使用前必須先賦值.

以下代碼會導致錯誤:

syaHi();//Uncaught ReferenceError: syaHi is not definedvar sayHi=function(){ alert("Hi!");}

不要像下面這樣寫代碼,這在ECMAScript中屬于無效語法,JavaScript引擎會嘗試修正錯誤,但不同瀏覽器修改不同.

//不要這樣做if(condition){ function sayHi(){  alert("Hi!"); }}else{ function sayHi(){  alert("Yo!"); }}

如果是使用函數表達式,就沒什么問題了.

//可以這樣做var sayHi;if(condition){ sayHi=function(){  alert("Hi!"); }}else{ sayHi=function(){  alert("Yo!"); }}

能夠創建函數再賦值給變量,也就能夠把函數作為其它函數的值返回.

function creatComparisonFunction(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;  } };}

creatComparisonFunction()就返回了一個匿名函數.返回的函數可能會被賦值給一個變量,或者以其他方式被調用;不過,在creatComparisonFunction()函數內部,它是匿名的.在把函數當成值來使用的情況下,都可以使用匿名函數.

7.1 遞歸

遞歸函數是一個函數通過名字調用自己的情況下構造的.

function factorial(num){ if(num<=1){  return 1; }else{  return num*factorial(num-1); }}

上面是一個經典的遞歸階乘函數.下面的代碼卻可能導致它出錯.

var anotherFactorial=factorial;factorial=null;alert(anotherFactorial(4));//Uncaught TypeError: factorial is not a function

以上代碼先把factorial()函數保存在變量anotherFactorial中,之后又將factorial變量設為null,結果指向原始引用只剩下一個.接下來調用anotherFactorial()時,由于必須執行factorial(),而factorial()已經不再是函數,所以會導致錯誤.

這種情況下,使用arguments.callee可以解決.

arguments.callee是一個指向正在執行的函數的指針,因此可以用它來實現對函數的遞歸調用.

function factorial(num){ if(num<=1){  return 1; }else{  return num*arguments.callee(num-1); }}

在編寫遞歸函數時,使用arguments.callee總比使用函數名更保險,因為它可以確保無論怎么調用函數都不會出問題.

但在嚴格模式下,不能通過腳本訪問arguments.callee.

不過可以使用函數表達式來達成相同的結果.

var factorial=(function f(num){ if(num<=1){  return 1; }else{  return num*f(num-1); }});console.log(factorial(4));//24

7.2 閉包

閉包是指有權訪問另一個函數作用域中的變量的函數.創建閉包的常見方式,就是在一個函數內部創建另一個函數.

function creatComparisonFunction(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.之所以還能夠訪問這個變量,是因為內部函數的作用域鏈中包含creatComparisonFunction()的作用域.

當某個函數被調用時,會創建一個執行環境(execution context)及相應的作用域鏈.然后,使用arguments和其他命名參數的值來初始化函數的活動對象(activation object).但在作用域鏈中,外部函數的活動對象始終處于第二位,外部函數的外部函數的活動對象處于第三位,....直至作為作用域鏈終點的全局執行環境.

在函數執行過程中,為讀取和寫入變量的值,就需要姑作用域鏈中查找變量.

function compare(value1,value2){  if(value1<value2){   return -1;  }else if(value1>value2){   return 1;  }else{   return 0;  } } var result=compare(5,10) console.log(result)//-1

以上代碼先定義了compare()函數,然后又在全局作用域中調用了它.當調用compare()時,會創建一個包含arguments,value1,value2的活動對象.全局執行環境的變量對象(包含result和compare)在compare()執行環境的作用域鏈中則處于第二位

后臺的每個執行環境都有一個表示變量的對象--變量對象.全局環境的變量對象始終存在,而像compare()函數這樣的局部環境的變量對象,則只在函數執行的過程中存在.在創建compare()函數時,會創建一個預先包含全局變量對象的作用域鏈,這個作用域鏈被保存在內部的[[Scope]]屬性中.當調用compare()函數時,會為函數創建一個執行環境,然后通過復制函數的[[Scope]]屬性中的對象構建起執行環境的作用域鏈.此后,又有一個活動對象(在此作為變量對象使用)被創建并被推入執行環境作用域鏈的前端.

作用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象.

無論什么時候在函數中訪問一個變量時,就會從作用域鏈中搜索具有相應名字的變量.一般來講,當函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域(全局執行環境的變量對象).但是,閉包的情況又有所不同.

在另一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的作用域中.

var compare=creatComparisonFunction("name");var result=comapre({name:"Nicholas"},{name:"Greg"});

下圖展示了上面代碼代碼執行時,包含函數與內部匿名函數的作用域.


當createComparisonFunction()函數返回后,其執行環境的作用域會被銷毀,但它的活動對象仍然會留在內存中;直到匿名函數被銷毀后,createComparisonFunction()的活動對象都會被銷毀.

//創建函數 var compare=creatComparisonFunction("name"); //調用函數 var result=comapre({name:"Nicholas"},{name:"Greg"}); //解除對匿名函數的引用(以便釋放內存) compareNames=null;

通過將compareNames設置為等于null解除該函數的引用,就等于通知垃圾回收例程將其清除.隨著匿名函數函數的作用域鏈被銷毀,其他作用域(除了全局作用域)也都可以安全地銷毀了.

由于閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存.過度使用閉包可能會導致內存占用過多,慎重使用閉包.

7.2.1 閉包和變量

作用域鏈的這種配置的機制引出了一個值得注意的副作用,即閉包只能取得包含函數中任何變量的最后一個值.

閉包里所保存的是整個變量對象,而不是某個特殊的變量.

function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){  result[i]=function(){   return i;  }; } return result;}

上面代碼里這個函數會返回一個函數數組.表面上看,似乎每個函數都應該返回自己的索引值,但實際上,每個函數都返回10.因為每個函數的作用域鏈中都保存著createFunctions()函數的活動對象,所以它們引用的都是同一個變量i.當createFunction()函數返回后,變量i的值是10,此時每個函數都引用著保存變量i的同一個變量對象,所以在每個函數內部i的值都是10.

但是,我們可以通過創建另一個匿名函數強制讓閉包的行為符合預期.

function createFunctions(){ var result=new Array(); for(var i=0;i<10;i++){  result[i]=function(num){   return function(){    return num;   }  }(i); } return result;}

重寫之后,每個函數就會返回各自不同的索引值了.在這個版本中,我們沒有直接把閉包賦值給數組,而是定義了一個匿名函數,并將立即執行匿名函數的結果賦給數組.這里的匿名函數有一個參數num,也就是最終的函數要返回的值.在調用每個匿名函數時,我們傳入了變量i.由于函數參數是按值傳遞的,所以就會將變量i的當前值復制給參數num.而在這個匿名函數內部,又創建并返回了一個訪問num的閉包.這個一來,result數組中的每個函數都有自己num變量的一個副本,因此就可以返回各自不同的數值了.

7.2.2 關于this對象

this對象是在運行時基本函數的執行環境綁定的:在全局函數中,this等于window,而當函數被作為某個對象的方法調用時,this等于那個對象.不過,匿名函數的執行環境具有全局性,因此其this對象通常指向window.

var name="the window";var object={ name:"my object", getNameFunc:function(){  return function(){   return this.name;  }; }};alert(object.getNameFunc()());//the window(在非嚴格模式下)

每個函數在被調用時都會自動取得兩個特殊變量:this和arguments.內部函數在搜索這兩個變量時,只會搜索到其活動對象為止,因此永遠不可能直接訪問外部函數中的這兩個變量.

不過,把外部作用域中的this對象保存在一個閉包能夠訪問到的變量里,就可以讓閉包訪問該對象了.

var name="the window";var object={ name:"my object", getNameFunc:function(){  var that=this;  return function(){   return that.name;  }; }};alert(object.getNameFunc()());//my object

在定義匿名函數之前,我們把this對象賦值給了一個名叫that的變量.而在定義了閉包之后,閉包也可以訪問這個變量,因為它是我們在包含函數中特意聲明的一個變量.即使在函數返回之后,that也仍然引用著object,所以調用object.getNameFunc()()就返回了my object.

注意:this和arguments也存在同樣的問題.如果想訪問作用域中的arguments對象,必須將對該對象的引用保存到另一個閉包能夠訪問的變量中.

var name="the window";var object={ name:"my object", getName:function(){  return this.name; }};console.log(object.getName());//my objectconsole.log((object.getName)());//my objectconsole.log((object.getName=object.getName)());//the window

最后一行代碼先執行了一條賦值語句,然后再調用賦值后的結果.因為這個賦值表達式的值是函數本身,所以this的值不能得到維持,結果就返回了"this window".

7.2.3 內存泄露

由于IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集例程,因此閉包在IE的這些版本中會導致一些特殊的問題.具體來說,如果閉包的作用域鏈中保存著一個HTML元素,那么就意味著該元素將無法被銷毀.

function assignHandlet(){ var element=document.getElementById("someElement"); element.onclick=function(){  alert(element.id); };}

以上代碼創建了一個作為element元素事件處理程序的閉包,而這個閉包則又創建了一個循環引用.由于匿名函數保存了一個對assignHandler()的活動對象的引用,因此應付導致無法減少element的引用數.只要匿名函數存在,element的引用數至少也是1,因此它所占用的內存就永遠不會被回收.不過這個問題可以通過稍微改寫一下代碼來解決.

function assignHandlet(){ var element=document.getElementById("someElement"); var id=element.id; element.onclick=function(){  alert(id); }; element=null;}

上面代碼中,通過把element.id的一個副本保存在一個變量中,并且在閉包中引用該變量消除了循環引用.

武林網友情提醒大家:閉包會引用包含函數的整個活動對象,而其中包含著element.即使閉包不直接引用element,包含函數的活動對象也仍然會保存一個引用.因此,有必要把element變量設置為null.這樣就能夠解除對DOM對象的引用,順利地減少其引用數,確保正?;厥掌湔加玫膬却?

下面給大家介紹下函數表達式。

在JavaScript 編程中,函數表達式是一種非常有用的技術。使用函數表達式可以無須對函數命名,從而實現動態編程。匿名函數,也稱為拉姆達函數,是一種使用JavaScript 函數的強大方式。以下總結了函數表達式的特點。

函數表達式不同于函數聲明。函數聲明要求有名字,但函數表達式不需要。沒有名字的函數表達式也叫做匿名函數。

在無法確定如何引用函數的情況下,遞歸函數就會變得比較復雜;遞歸函數應該始終使用arguments.callee 來遞歸地調用自身,不要使用函數名――函數名可能會發生變化。

當在函數內部定義了其他函數時,就創建了閉包。閉包有權訪問包含函數內部的所有變量,原理
如下。

在后臺執行環境中,閉包的作用域鏈包含著它自己的作用域、包含函數的作用域和全局作用域。通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀。

但是,當函數返回了一個閉包時,這個函數的作用域將會一直在內存中保存到閉包不存在為止。

使用閉包可以在JavaScript 中模仿塊級作用域(JavaScript 本身沒有塊級作用域的概念),要點如下。

創建并立即調用一個函數,這樣既可以執行其中的代碼,又不會在內存中留下對該函數的引用。
結果就是函數內部的所有變量都會被立即銷毀――除非將某些變量賦值給了包含作用域(即外部作用域)中的變量。
閉包還可以用于在對象中創建私有變量,相關概念和要點如下。即使JavaScript 中沒有正式的私有對象屬性的概念,但可以使閉包來實現公有方法,而通過公有方法可以訪問在包含作用域中定義的變量。
有權訪問私有變量的公有方法叫做特權方法。

可以使用構造函數模式、原型模式來實現自定義類型的特權方法,也可以使用模塊模式、增強的模塊模式來實現單例的特權方法。

JavaScript 中的函數表達式和閉包都是極其有用的特性,利用它們可以實現很多功能。不過,因為創建閉包必須維護額外的作用域,所以過度使用它們可能會占用大量內存。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品白丝jk喷水视频一区| 欧美日韩国产一区中文午夜| 久久亚洲精品国产亚洲老地址| 欧美成年人网站| 精品国产一区久久久| 亚洲美女又黄又爽在线观看| 中文字幕国产日韩| 精品动漫一区二区| 精品国产一区二区在线| 一区二区三区黄色| 欧美大片va欧美在线播放| 国产精品精品视频| 日韩av在线免费观看| 亚洲第一国产精品| 日韩中文字幕免费视频| 精品国产福利在线| 69国产精品成人在线播放| 亚洲另类欧美自拍| 国产精品久久久久久久久久尿| 日韩欧美中文字幕在线观看| 热99久久精品| 精品亚洲国产视频| 久久久久久久激情视频| 97涩涩爰在线观看亚洲| 欧日韩不卡在线视频| 久久久久国产精品免费网站| 欧美区二区三区| 91av在线播放视频| 国产精品久久久久免费a∨| 国产美女高潮久久白浆| 日韩av在线看| 精品欧美aⅴ在线网站| x99av成人免费| 国产欧美日韩最新| 亚洲网站视频福利| 日韩欧美在线一区| 成人一区二区电影| 国产精品久久久久久久av大片| 国产99视频在线观看| 精品欧美aⅴ在线网站| 亚洲在线免费观看| 91亚洲精品在线| 国产精品极品美女粉嫩高清在线| 亚洲黄页网在线观看| 亚洲欧洲午夜一线一品| 日韩国产在线播放| 亚洲国产精品999| 最新国产成人av网站网址麻豆| 国产精品扒开腿做爽爽爽视频| 日韩av综合网| 国产精品9999| 亚州精品天堂中文字幕| 日韩av手机在线观看| 亚洲精品乱码久久久久久按摩观| 国产综合在线观看视频| 午夜精品久久久久久久99热浪潮| 日韩精品在线第一页| 91中文精品字幕在线视频| 日韩在线中文字| 国产精品美女网站| 国产综合久久久久久| 伊人久久免费视频| 亚洲精品女av网站| 5566成人精品视频免费| 26uuu久久噜噜噜噜| 亚洲人成电影网| 国产一区二区三区久久精品| 国产有码在线一区二区视频| 成人午夜在线影院| 九九精品在线视频| 精品视频久久久久久| 高清在线视频日韩欧美| 5566成人精品视频免费| 国产精欧美一区二区三区| 国产精品久久久久久久久久| 日韩在线观看视频免费| 日韩精品在线免费观看视频| 中文字幕精品视频| 国产亚洲美女久久| 亚洲福利视频免费观看| 亚洲成人久久久| 久久精品91久久香蕉加勒比| 97人人模人人爽人人喊中文字| 2018国产精品视频| 日韩精品久久久久久福利| 成人黄色av免费在线观看| 红桃视频成人在线观看| 中文字幕在线日韩| 欧美大片网站在线观看| 久久久精品一区| 在线观看欧美视频| 国产成+人+综合+亚洲欧美丁香花| 精品国产户外野外| 欧美成人精品激情在线观看| 久久久99免费视频| 中文字幕精品久久久久| 狠狠躁夜夜躁人人爽天天天天97| 欧美精品久久久久久久久久| 中文字幕免费精品一区| 久久久久久久久综合| 欧美精品免费看| 日本久久久久亚洲中字幕| 欧美激情图片区| 91久久精品国产| 久久久精品中文字幕| 日韩中文在线不卡| 国产成人极品视频| 九九热这里只有精品免费看| 日韩欧美在线观看视频| 97av在线影院| 91av在线免费观看视频| 久久久久这里只有精品| 精品国产一区二区三区久久狼5月| 怡红院精品视频| 91精品国产精品| 1769国内精品视频在线播放| 久久久精品视频成人| 成人h视频在线| 久久精品国产一区二区电影| 精品久久久久久亚洲国产300| 亚洲色在线视频| 久久久免费电影| 国产精品亚洲аv天堂网| 日韩av在线网址| 在线日韩欧美视频| 2024亚洲男人天堂| 久久免费精品日本久久中文字幕| 欧美精品一区二区三区国产精品| 91av在线免费观看| 亚洲国产精品专区久久| 亚洲伊人成综合成人网| 久久久久久亚洲精品不卡| 亚洲人成网站777色婷婷| 国产精品亚发布| 日韩一区在线视频| 成人午夜在线视频一区| 成人欧美一区二区三区黑人| 亚洲综合在线中文字幕| 欧美日韩国产麻豆| 亚洲欧美激情精品一区二区| 亚洲成色777777女色窝| 久久国内精品一国内精品| 欧美成人免费一级人片100| 久久在线视频在线| 成人国产精品一区二区| 国产精品影院在线观看| 永久免费毛片在线播放不卡| 91爱视频在线| 91亚洲一区精品| 亚洲成人av资源网| 亚洲午夜女主播在线直播| 日韩av在线影视| 欧美激情免费观看| 亚洲国产精品专区久久| 亚洲天堂av电影| 97在线视频观看| 成人在线免费观看视视频| 国产精品小说在线| 国产精品久久久久久久久借妻| 日本一本a高清免费不卡| 亚洲人成网站999久久久综合| 日韩欧美国产免费播放| 欧美在线免费视频| 91高清免费在线观看|