1. 程式人生 > >JavaScript高階程式設計(讀書筆記)之函式表示式

JavaScript高階程式設計(讀書筆記)之函式表示式

定義函式的方式有兩種:一種是函式宣告,另一種就是函式表示式。

函式宣告的一個重要特徵就是函式宣告提升(function declaration hoisting),意思是在執行程式碼前會先讀取函式宣告。

這個例子不會報錯,因為程式碼在執行前會先讀取函式宣告。理解函式提升的關鍵就是區別函式宣告和函式表示式之間的區別。
函式表示式的方式有幾種表達形式,其中很常見的一種形式就是匿名函式的形式:

這種情況下建立的函式叫做匿名函式,因為function關鍵字後面沒有識別符號。匿名函式的name屬性是空字串。

無論什麼時候在函式中訪問一個變數時,都會從作用域鏈中搜索具有相應名字的變數。一般來講,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅儲存全域性作用域(全域性執行環境的變數物件)。但是,閉包的情況又有所不同。 在另一個函式內部定義的函式將會包含函式(即外部函式)的活動物件新增到它的作用域鏈中。因此,在createComparisonFunction()函式內部定義的匿名函式的作用域鏈中,實際上將會包含外部函createComparisonFunction()的活動物件。下圖展示了當下列程式碼執行時,包含函式與內部匿名函式的作用域鏈。 **當createComparisonFunction()函式返回後,其執行環境的作用域鏈會被銷燬,但它的活動物件仍然會留在記憶體中;知道匿名函式被銷燬後,createComparisonFunction()的活動物件才會被銷燬** 閉包的特點:由於閉包會攜帶包含它的函式的作用域,因此會比其他函式佔用更多的記憶體。過度使用閉包會導致記憶體佔用過多。 **作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函式中任何變數的最後一個值。閉包所儲存的是整個變數物件,而不是某個特殊的值** 每個函式的返回值都是10. 我們可以通過建立另一個匿名函式強制讓閉包的行為符合預期, 

英語縮略詞這裡匿名函式有一個引數num,也就是最終要返回的引數,在呼叫每個函式時,我們傳入了變數i。由於引數是傳遞的,會將變數i的當前值複製給引數num,而在這個匿名函式內部,又建立並返回一個訪問num的閉包,因此,result陣列中的每個函式都有自己num變數的一個副本。 關於this物件 因為**匿名函式**(非匿名函式則this指標指向當前執行環境)的執行環境具有全域性性,因此其this指標通常指向window。如果想訪問作用域中的arguments物件,必須將物件的引用儲存到另一個閉包能夠訪問的變數中。

JavaScript從來不會告訴你是否多次聲明瞭同一個變數;遇到這種情況,它只會對後續的宣告視而不見(不過,他會執行後續宣告中的變數初始化)。匿名函式可以用來模仿塊級作用域並避免這個問題。


用作塊級作用域(通常稱為私用作用域)的匿名函式的語法如下所示。

以上程式碼定義並立即呼叫了一個匿名函式。將函式宣告包含在一對圓括號中,表示它實際是一個函式表示式。而緊隨其後的圓括號會立即呼叫這個函式。需要注意的是函式聲明後不能跟圓括號而函式表示式是可以的:

在匿名函式中定義的任何變數,都會在執行結束時銷燬。無論在什麼地方,只要臨時需要一些變數,廣州英語培訓就可以使用私有作用域,例如:

這種技術經常在全域性作用域中被用在函式外部,從而限制向全域性作用域中新增過多的變數和函式。一般來說,我們都應該儘量少向全域性作用域中新增變數和函式。在一個有很多開發人員共同參與的大型應用程式中,過多的全域性變數和函式很容易導致命名衝突。而通過建立私有作用域,每個開發人員既可以使用自己的變數,又不必擔心搞亂全域性作用域。

(使用建構函式) 嚴格來講,JavaScript中沒有私有成員的概念;所有物件屬性都是公有的。不過,倒是有一個私有變數的概念。任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式的外部訪問這些變數。私有變數包括函式的引數、區域性變數和在函式內部定義的其它函式。 如果在函式內部建立閉包,那麼閉包通過自己的作用域鏈也可以訪問這些變數。而利用這一點,就可以建立用於訪問私有變數的公有方法。 我們把有權訪問私有變數和私有函式的公有方法稱為特權方法(privileged method)。使用私有和特權成員,可以隱藏那些不應該被直接修改的資料: 在Perso建構函式的外部,沒有任何方法訪問name。由於這兩個方法是在建構函式內部定義的,它們作為閉包能夠通過作用域鏈訪問name。建構函式定義特權方法也有一個缺點,就是必須使用建構函式模式來達到這個目的。而建構函式模式的缺點是針對每個例項都會建立同樣一組新方法,而使用靜態私有變數來實現特權方法就可以避免這個問題。 (使用匿名函式巢狀) 通過在私有作用域中定義私有變數或函式,同樣也可以建立特權方法,其基本模式如下: 這個模式建立了一個私有作用域,並在其中封裝了一個建構函式及相應的方法。在私有作用域中,首先定義了私有變數和私有函式,然後又定義了建構函式及其公有方法。公有方法是在原型上定義的,這一點體現了典型的原型模式。**需要注意的是,在定義建構函式時並沒有使用函式宣告,而是使用函式表示式,函式宣告只能建立區域性函式,處於同樣原因,沒有在宣告MyObject時使用var關鍵字,記住:初始化未經宣告的變數,總是會建立一個全域性變數,MyObject就成為了一個全域性變數,能夠在私有作用域之外被訪問**。這個模式與在建構函式中定義特權方法的主要區別,就在於**私有變數和函式是由例項共享的**。由於特權方法是在原型上定義的,因此所有例項都使用同一個函式。而這個特權方法,作為一個閉包,總是儲存著對包含作用域的引用。 在一個例項上呼叫setname()會影響所有例項。**呼叫setName()或建立一個Person例項都會賦予name屬性一個新值。結果就是所有例項都會返回相同的值。**以這種方式建立靜態私有變數會因為使用原型而增進程式碼複用,但每個例項都沒有自己的私有變數。到底是使用例項變數,還是靜態私有變數,最終還是要視你的具體需求而定。

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

函式表示式不同於函式宣告。函式宣告要求有名字,但函式表示式不需要。沒有名字的函式表示式也叫作匿名函式;
在無法確定如何引用函式的情況下,遞迴函式就會變得比較複雜;
遞迴函式應該始終使用argument.callee來遞迴呼叫自身,不要使用函式名——函式名可能會發生變化。
當函式內部定義了其它函式時,就建立了閉包。閉包有權訪問包含函式內部的所有變數,原理如下。

在後臺執行環境中,閉包的作用域鏈包含著它自己的作用域、包含函式的作用域和全域性作用域;
通常,函式的作用域及其所有變數都會在函式執行結束後被銷燬;
但是,當函式返回了一個閉包時,這個函式的作用域將會一直在記憶體中儲存到閉包不存在為止。
使用閉包可以在JavaScript中模仿塊級作用域(JavaScript本身沒有塊級作用域的概念),要點如下:

建立並立即呼叫一個函式,這樣既可以執行其中的程式碼,又不會在記憶體中留下對該函式的引用。
結果就是函式內部的所有變數都會被立即銷燬——除非將某些變數賦值給了包含作用域(即外部作用域)中的變數。
閉包還可以用於在物件中建立私有變數,相關概念和要點如下:

及時JavaScript中沒有正式的私有物件屬性的概念,但可以使用閉包來實現公有方法,雅思教材而通過公有方法可以訪問在包含作用域中定義的變數;
有權訪問私有變數的公有方法叫做特權方法;
可以使用建構函式模式、原型模式來實現自定義型別的特權方法,也可以使用塊級模式、增強的模組模式來實現單例的特權方法。
JavaScript中的函式表示式和閉包都是極其有用的特性,利用它們可以實現很多功能。不過,因為建立閉包必須維護額外的作用域,所以過度使用它們可能會佔用大量記憶體。

詳見《JavaScript高階程式設計》3rd Edition