天天看點

JavaScript 基礎知識6 函數表達式

JavaScript 基礎知識6 函數表達式

我們在前面章節使用的文法稱為 函數聲明:

function sayHi() {

alert( "Hello" );

}

另一種建立函數的文法稱為 函數表達式。

通常會寫成這樣:

let sayHi = function() {

};

在這裡,函數被建立并像其他指派一樣,被明确地配置設定給了一個變量。不管函數是被怎樣定義的,都隻是一個存儲在變量 sayHi 中的值。

上面這兩段示例代碼的意思是一樣的:“建立一個函數,并把它存進變量 sayHi”。

我們還可以用 alert 列印這個變量值:

alert( sayHi ); // 顯示函數代碼

注意,最後一行代碼并不會運作函數,因為 sayHi 後沒有括号。在某些程式設計語言中,隻要提到函數的名稱都會導緻函數的調用執行,但 JavaScript 可不是這樣。

在 JavaScript 中,函數是一個值,是以我們可以把它當成值對待。上面代碼顯示了一段字元串值,即函數的源碼。

的确,在某種意義上說一個函數是一個特殊值,我們可以像 sayHi() 這樣調用它。

但它依然是一個值,是以我們可以像使用其他類型的值一樣使用它。

我們可以複制函數到其他變量:

function sayHi() { // (1) 建立

let func = sayHi; // (2) 複制

func(); // Hello // (3) 運作複制的值(正常運作)!

sayHi(); // Hello // 這裡也能運作(為什麼不行呢)

解釋一下上段代碼發生的細節:

(1) 行聲明建立了函數,并把它放入到變量 sayHi。

(2) 行将 sayHi 複制到了變量 func。請注意:sayHi 後面沒有括号。如果有括号,func = sayHi() 會把 sayHi() 的調用結果寫進func,而不是 sayHi 函數 本身。

現在函數可以通過 sayHi() 和 func() 兩種方式進行調用。

注意,我們也可以在第一行中使用函數表達式來聲明 sayHi:

let func = sayHi;

// ...

這兩種聲明的函數是一樣的。

為什麼這裡末尾會有個分号?

你可能想知道,為什麼函數表達式結尾有一個分号 ;,而函數聲明沒有:

// ...

答案很簡單:

在代碼塊的結尾不需要加分号 ;,像 if { ... },for { },function f { } 等文法結構後面都不用加。

函數表達式是在語句内部的:let sayHi = ...;,作為一個值。它不是代碼塊而是一個指派語句。不管值是什麼,都建議在語句末尾添加分号 ;。是以這裡的分号與函數表達式本身沒有任何關系,它隻是用于終止語句。

回調函數

讓我們多舉幾個例子,看看如何将函數作為值來傳遞以及如何使用函數表達式。

我們寫一個包含三個參數的函數 ask(question, yes, no):

question

關于問題的文本

yes

當回答為 “Yes” 時,要運作的腳本

no

當回答為 “No” 時,要運作的腳本

函數需要提出 question(問題),并根據使用者的回答,調用 yes() 或 no():

function ask(question, yes, no) {

if (confirm(question)) yes()

else no();

function showOk() {

alert( "You agreed." );

function showCancel() {

alert( "You canceled the execution." );

// 用法:函數 showOk 和 showCancel 被作為參數傳入到 ask

ask("Do you agree?", showOk, showCancel);

在實際開發中,這樣的函數是非常有用的。實際開發與上述示例最大的差別是,實際開發中的函數會通過更加複雜的方式與使用者進行互動,而不是通過簡單的 confirm。在浏覽器中,這樣的函數通常會繪制一個漂亮的提問視窗。但這是另外一件事了。

ask 的兩個參數值 showOk 和 showCancel 可以被稱為 回調函數 或簡稱 回調。

主要思想是我們傳遞一個函數,并期望在稍後必要時将其“回調”。在我們的例子中,showOk 是回答 “yes” 的回調,showCancel 是回答 “no” 的回調。

我們可以用函數表達式對同樣的函數進行大幅簡寫:

ask(

"Do you agree?",

function() { alert("You agreed."); },

function() { alert("You canceled the execution."); }

);

這裡直接在 ask(...) 調用内進行函數聲明。這兩個函數沒有名字,是以叫 匿名函數。這樣的函數在 ask 外無法通路(因為沒有對它們配置設定變量),不過這正是我們想要的。

這樣的代碼在我們的腳本中非常常見,這正符合 JavaScript 語言的思想。

一個函數是表示一個“行為”的值

字元串或數字等正常值代表 資料。

函數可以被視為一個 行為(action)。

我們可以在變量之間傳遞它們,并在需要時運作。

函數表達式 vs 函數聲明

讓我們來總結一下函數聲明和函數表達式之間的主要差別。

首先是文法:如何通過代碼對它們進行區分。

函數聲明:在主代碼流中聲明為單獨的語句的函數。

// 函數聲明

function sum(a, b) {

return a + b;

函數表達式:在一個表達式中或另一個文法結構中建立的函數。下面這個函數是在指派表達式 = 右側建立的:

// 函數表達式

let sum = function(a, b) {

更細微的差别是,JavaScript 引擎會在 什麼時候 建立函數。

函數表達式是在代碼執行到達時被建立,并且僅從那一刻起可用。

一旦代碼執行到指派表達式 let sum = function… 的右側,此時就會開始建立該函數,并且可以從現在開始使用(配置設定,調用等)。

函數聲明則不同。

在函數聲明被定義之前,它就可以被調用。

例如,一個全局函數聲明對整個腳本來說都是可見的,無論它被寫在這個腳本的哪個位置。

這是内部算法的原故。當 JavaScript 準備 運作腳本時,首先會在腳本中尋找全局函數聲明,并建立這些函數。我們可以将其視為“初始化階段”。

在處理完所有函數聲明後,代碼才被執行。是以運作時能夠使用這些函數。

例如下面的代碼會正常工作:

sayHi("John"); // Hello, John

function sayHi(name) {

alert( `Hello, ${name}` );

函數聲明 sayHi 是在 JavaScript 準備運作腳本時被建立的,在這個腳本的任何位置都可見。

……如果它是一個函數表達式,它就不會工作:

sayHi("John"); // error!

let sayHi = function(name) { // (*) no magic any more

函數表達式在代碼執行到它時才會被建立。隻會發生在 (*) 行。為時已晚。

函數聲明的另外一個特殊的功能是它們的塊級作用域。

嚴格模式下,當一個函數聲明在一個代碼塊内時,它在該代碼塊内的任何位置都是可見的。但在代碼塊外不可見。

例如,想象一下我們需要依賴于在代碼運作過程中獲得的變量 age 聲明一個函數 welcome()。并且我們計劃在之後的某個時間使用它。

如果我們使用函數聲明,則以下代碼無法像預期那樣工作:

let age = prompt("What is your age?", 18);

// 有條件地聲明一個函數

if (age < 18) {

function welcome() {

alert("Hello!");

}

} else {

alert("Greetings!");

// ……稍後使用

welcome(); // Error: welcome is not defined

這是因為函數聲明隻在它所在的代碼塊中可見。

下面是另一個例子:

let age = 16; // 拿 16 作為例子

welcome(); // \ (運作)

// |

function welcome() { // |

alert("Hello!"); // | 函數聲明在聲明它的代碼塊内任意位置都可用

} // |

welcome(); // / (運作)

// 在這裡,我們在花括号外部調用函數,我們看不到它們内部的函數聲明。

我們怎麼才能讓 welcome 在 if 外可見呢?

正确的做法是使用函數表達式,并将 welcome 指派給在 if 外聲明的變量,并具有正确的可見性。

下面的代碼可以如願運作:

let welcome;

welcome = function() {

};

welcome(); // 現在可以了

或者我們可以使用問号運算符 ? 來進一步對代碼進行簡化:

let welcome = (age < 18) ?

function() { alert("Hello!"); } :

function() { alert("Greetings!"); };

什麼時候選擇函數聲明與函數表達式?

根據經驗,當我們需要聲明一個函數時,首先考慮函數聲明文法。它能夠為組織代碼提供更多的靈活性。因為我們可以在聲明這些函數之前調用這些函數。

這對代碼可讀性也更好,因為在代碼中查找 function f(…) {…} 比 let f = function(…) {…} 更容易。函數聲明更“醒目”。

繼續閱讀