JavaScript中建立函數主要有兩種方法:函式宣告和函數運算式。這兩種方式都有不同的適用情境。這篇筆記主要關注的是函數運算式的幾大特點以及它的使用情境,下面一一描述。
主要特點
•可選的函數名稱
函數名稱是函式宣告的必需組成部分,這個函數名稱相當於一個變數,新定義的函數會複製給這個變數,以後函數的調用都需要通過這個變數進行。而對於函數運算式來說,函數的名稱是可選的,例如下面的例子:
var sub = function(a1,a2){ return a1-a2; }
這個例子中函數運算式沒有名稱,屬於匿名函數運算式。再看下面的例子:
var sub = function f(a1,a2){ return a1-a2; } console.log(f(5,3)); //錯誤調用方式 console.log(sub(5,3)); //正確調用方式
在這個例子中,函數運算式的名稱為f,這個名稱f實際上變成了函數內部的一個局部變數,並且指代函數對象本身,在函數遞迴的時候有很大用處,後面會詳細講到。
•在執行階段建立(區別於函式宣告)
這個特點是函數運算式明顯區別於函式宣告的地方。
解譯器在解析JavaScript代碼時對於這兩種方式並不是一視同仁的。解譯器會首先讀取函式宣告,並使其在執行任何代碼之前可用;而對於函數運算式,則必須等到解譯器執行到它所在的程式碼,才會被真正解析執行。例如:
console.log(add(1,2)); //"3" console.log(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){ console.log(typeof f); //"function" return a1-a2; } console.log(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; console.log(anotherFactorial(5)); //"Uncaught TypeError: factorial is not a function"
報錯的原因在於在函數體內部會調用factorial函數,而變數factorial對函數的引用已經被解除所以報錯。這種情況的解決方案一般可以使用arguments.callee來解決,arguments.callee始終指向當前的函數,例如:
function factorial(num){ if(num <= 1){ return 1; }else{ return num * arguments.callee(num - 1); } }
這樣在此執行anotherFactorial函數就可以得到正確結果了。但是在strict 模式"strict"下,arguments.callee是不能通過指令碼訪問的,這是就可以使用函數運算式來解決這個問題了,例如:
var factorial = (function f(num){ if(num <= 1){ return 1; }else{ return num * f(num - 1); } }); console.log(factorial(5)); //"120"
•代碼模組化
JavaScript中沒有塊級範圍,但我們可以使用函數運算式模組化JavaScript代碼。模組化代碼中可以封裝不必讓使用者知道的細節,只暴露給使用者相關介面,同時可以避免對全域環境的汙染,例如:
var person = (function(){ var _name = ""; return{ getName:function(){ return _name; }, setName:function(newname){ _name = newname; } }; }()); person.setName("John"); person.getName(); //"John"
這個例子中建立了一個匿名函數運算式,這個函數運算式中包含了模組自身的私人變數和函數;這個函數運算式的執行結果返回一個對象,對象中包含了模組暴露給使用者的公用介面。代碼模組化的具體形式還有很多,例如在一些常用的JavaScript庫中通常都會使用類似下面例子的立即執行函數:
(function(){ var _name = ""; var root = this; var person = { getName: function(){ return _name; }, setName: function(newname){ _name = newname; } }; root.person = person; }.call(this)); person.setName("John"); person.getName(); //"John"
這種方式直接將包含模組公用介面的對象作為全域對象的一個屬性,這樣在其它地方直接可以使用全域對象的這個屬性來使用這個模組了。
以上這篇老生常談JavaScript 函數運算式就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援雲棲社區。