當前位置:才華齋>設計>網頁設計>

JavaScript 函式表示式

網頁設計 閱讀(2.98W)

JavaScript中建立函式主要有兩種方法:函式宣告和函式表示式。這兩種方式都有不同的適用場景。這篇筆記主要關注的是函式表示式的幾大特點以及它的使用場景,下面一一描述。

JavaScript 函式表示式

主要特點

可選的函式名稱

函式名稱是函式宣告的必需組成部分,這個函式名稱相當於一個變數,新定義的函式會複製給這個變數,以後函式的呼叫都需要通過這個變數進行。而對於函式表示式來說,函式的名稱是可選的,例如下面的例子:

var sub = function(a1,a2){ return a1-a2; }

這個例子中函式表示式沒有名稱,屬於匿名函式表示式。再看下面的例子:

var sub = function f(a1,a2){ return a1-a2; } (f(5,3)); //錯誤呼叫方式 (sub(5,3)); //正確呼叫方式

在這個例子中,函式表示式的名稱為f,這個名稱f實際上變成了函式內部的一個區域性變數,並且指代函式物件本身,在函式遞迴的時候有很大用處,後面會詳細講到。

在執行階段建立(區別於函式宣告)

這個特點是函式表示式明顯區別於函式宣告的地方。

直譯器在解析JavaScript程式碼時對於這兩種方式並不是一視同仁的。直譯器會首先讀取函式宣告,並使其在執行任何程式碼之前可用;而對於函式表示式,則必須等到直譯器執行到它所在的程式碼行,才會被真正解析執行。例如:

(add(1,2)); //"3" (sub(5,3)); //"unexpected identifier",報錯 function add(a1,a2){ return a1+a2; } var sub = function(a1,a2){ return a1-a2; }

第一條語句完全可以正常執行。對程式碼求值時,JavaScript引擎在第一遍就會宣告函式並通過一個名為函式宣告提升的過程將它們放到原始碼樹的頂部。也就是說在執行環境的建立階段(函式被呼叫但還沒有開始執行)就會對函式宣告進行"hosting"操作。所以,即使宣告函式的程式碼在呼叫它的程式碼後面,JavaScript引擎也會把函式宣告提升到頂部。但是如果把函式宣告更改為函式表示式,就會在執行期間報錯。原因在於在執行到函式所在的語句之前,變數sub中並不會包含對函式的引用。也就是說在程式碼執行階段,變數sub才會被賦值。除了以上不同,在其它方面函式宣告和函式表示式的語法是等價的。

不會影響變數物件

var sub = function f(a1,a2){ (typeof f); //"function" return a1-a2; } (typeof f); //"Uncaught ReferenceError: f is not defined(…)"

通過上面的`例子可以看到,函式名稱f只能在函式物件內部使用,函式表示式的函式名稱並不存在於變數物件中。

使用場景

函式表示式的使用場景很多。下面主要描述的是函式遞迴以及程式碼模組化方面的應用。

函式遞迴

看下面的例子:

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

這是一個經典的階乘函式,但是這個例子存在的一個問題是函式名稱factorial與函式體緊密耦合在一起,執行下面的語句就會報錯:

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

報錯的原因在於在函式體內部會呼叫factorial函式,而變數factorial對函式的引用已經被解除所以報錯。這種情況的解決方法一般可以使用ee來解決,ee始終指向當前的函式,例如:

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

這樣在此執行anotherFactorial函式就可以得到正確結果了。但是在嚴格模式"strict"下,ee是不能通過指令碼訪問的,這是就可以使用函式表示式來解決這個問題了,例如:

var factorial = (function f(num){ if(num <= 1){ return 1; }else{ return num * f(num - 1); } }); (factorial(5)); //"120"

程式碼模組化

JavaScript中沒有塊級作用域,但我們可以使用函式表示式模組化JavaScript程式碼。模組化程式碼中可以封裝不必讓使用者知道的細節,只暴露給使用者相關介面,同時可以避免對全域性環境的汙染,例如:

var person = (function(){ var _name = ""; return{ getName:function(){ return _name; }, setName:function(newname){ _name = newname; } }; }()); ame("John"); ame(); //"John"

這個例子中建立了一個匿名函式表示式,這個函式表示式中包含了模組自身的私有變數和函式;這個函式表示式的執行結果返回一個物件,物件中包含了模組暴露給使用者的公共介面。程式碼模組化的具體形式還有很多,例如在一些常用的JavaScript庫中通常都會使用類似下面例子的立即執行函式:

(function(){ var _name = ""; var root = this; var person = { getName: function(){ return _name; }, setName: function(newname){ _name = newname; } }; on = person; }(this)); ame("John"); ame(); //"John"

這種方式直接將包含模組公共介面的物件作為全域性物件的一個屬性,這樣在其它地方直接可以使用全域性物件的這個屬性來使用這個模組了。