Javascript 函數(shù)表達(dá)式

2023-02-17 10:37 更新

在 JavaScript 中,函數(shù)不是“神奇的語言結(jié)構(gòu)”,而是一種特殊的值。

我們在前面章節(jié)使用的語法稱為 函數(shù)聲明

function sayHi() {
  alert( "Hello" );
}

另一種創(chuàng)建函數(shù)的語法稱為 函數(shù)表達(dá)式。

它允許我們在任何表達(dá)式的中間創(chuàng)建一個(gè)新函數(shù)。

例如:

let sayHi = function() {
  alert( "Hello" );
};

在這里我們可以看到變量 sayHi 得到了一個(gè)值,新函數(shù) function() { alert("Hello"); }。

由于函數(shù)創(chuàng)建發(fā)生在賦值表達(dá)式的上下文中(在 = 的右側(cè)),因此這是一個(gè) 函數(shù)表達(dá)式

請注意,function 關(guān)鍵字后面沒有函數(shù)名。函數(shù)表達(dá)式允許省略函數(shù)名。

這里我們立即將它賦值給變量,所以上面的兩個(gè)代碼示例的含義是一樣的:“創(chuàng)建一個(gè)函數(shù)并將其放入變量 sayHi 中”。

在更多更高階的情況下,稍后我們會(huì)遇到,可以創(chuàng)建一個(gè)函數(shù)并立即調(diào)用,或者安排稍后執(zhí)行,而不是存儲在任何地方,因此保持匿名。

函數(shù)是一個(gè)值

重申一次:無論函數(shù)是如何創(chuàng)建的,函數(shù)都是一個(gè)值。上面的兩個(gè)示例都在 sayHi 變量中存儲了一個(gè)函數(shù)。

我們還可以用 alert 顯示這個(gè)變量的值:

function sayHi() {
  alert( "Hello" );
}

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

注意,最后一行代碼并不會(huì)運(yùn)行函數(shù),因?yàn)?nbsp;sayHi 后沒有括號。在某些編程語言中,只要提到函數(shù)的名稱都會(huì)導(dǎo)致函數(shù)的調(diào)用執(zhí)行,但 JavaScript 可不是這樣。

在 JavaScript 中,函數(shù)是一個(gè)值,所以我們可以把它當(dāng)成值對待。上面代碼顯示了一段字符串值,即函數(shù)的源碼。

的確,在某種意義上說一個(gè)函數(shù)是一個(gè)特殊值,我們可以像 sayHi() 這樣調(diào)用它。

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

我們可以復(fù)制函數(shù)到其他變量:

function sayHi() {   // (1) 創(chuàng)建
  alert( "Hello" );
}

let func = sayHi;    // (2) 復(fù)制

func(); // Hello     // (3) 運(yùn)行復(fù)制的值(正常運(yùn)行)!
sayHi(); // Hello    //     這里也能運(yùn)行(為什么不行呢)

解釋一下上段代碼發(fā)生的細(xì)節(jié):

  1. ?(1)? 行聲明創(chuàng)建了函數(shù),并把它放入到變量 ?sayHi?。
  2. ?(2)? 行將 ?sayHi ?復(fù)制到了變量 ?func?。請注意:?sayHi ?后面沒有括號。如果有括號,?func = sayHi()? 會(huì)把 ?sayHi()? 的調(diào)用結(jié)果寫進(jìn)?func?,而不是 ?sayHi ?函數(shù) 本身。
  3. 現(xiàn)在函數(shù)可以通過 ?sayHi()? 和 ?func()? 兩種方式進(jìn)行調(diào)用。

我們也可以在第一行中使用函數(shù)表達(dá)式來聲明 ?sayHi?:

let sayHi = function() { // (1) 創(chuàng)建
  alert( "Hello" );
};

let func = sayHi;
// ...

這兩種聲明的函數(shù)是一樣的。

為什么這里末尾會(huì)有個(gè)分號?

你可能想知道,為什么函數(shù)表達(dá)式結(jié)尾有一個(gè)分號 ;,而函數(shù)聲明沒有:

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

答案很簡單:這里函數(shù)表達(dá)式是在賦值語句 let sayHi = ...; 中以 function(…) {…} 的形式創(chuàng)建的。建議在語句末尾加上分號 ;,它不是函數(shù)語法的一部分。

分號用于更簡單的賦值,例如 let sayHi = 5;,它也用于函數(shù)賦值。

回調(diào)函數(shù)

讓我們多舉幾個(gè)例子,看看如何將函數(shù)作為值來傳遞以及如何使用函數(shù)表達(dá)式。

我們寫一個(gè)包含三個(gè)參數(shù)的函數(shù) ask(question, yes, no)

?question
?

關(guān)于問題的文本

?yes
?

當(dāng)回答為 “Yes” 時(shí),要運(yùn)行的腳本

?no
?

當(dāng)回答為 “No” 時(shí),要運(yùn)行的腳本

函數(shù)需要提出 question(問題),并根據(jù)用戶的回答,調(diào)用 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." );
}

// 用法:函數(shù) showOk 和 showCancel 被作為參數(shù)傳入到 ask
ask("Do you agree?", showOk, showCancel);

在實(shí)際開發(fā)中,這樣的函數(shù)是非常有用的。實(shí)際開發(fā)與上述示例最大的區(qū)別是,實(shí)際開發(fā)中的函數(shù)會(huì)通過更加復(fù)雜的方式與用戶進(jìn)行交互,而不是通過簡單的 confirm。在瀏覽器中,這樣的函數(shù)通常會(huì)繪制一個(gè)漂亮的提問窗口。但這是另外一件事了。

ask 的兩個(gè)參數(shù)值 showOk 和 showCancel 可以被稱為 回調(diào)函數(shù) 或簡稱 回調(diào)

主要思想是我們傳遞一個(gè)函數(shù),并期望在稍后必要時(shí)將其“回調(diào)”。在我們的例子中,showOk 是回答 “yes” 的回調(diào),showCancel 是回答 “no” 的回調(diào)。

我們可以使用函數(shù)表達(dá)式來編寫一個(gè)等價(jià)的、更簡潔的函數(shù):

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "Do you agree?",
  function() { alert("You agreed."); },
  function() { alert("You canceled the execution."); }
);

這里直接在 ask(...) 調(diào)用內(nèi)進(jìn)行函數(shù)聲明。這兩個(gè)函數(shù)沒有名字,所以叫 匿名函數(shù)。這樣的函數(shù)在 ask 外無法訪問(因?yàn)闆]有對它們分配變量),不過這正是我們想要的。

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

一個(gè)函數(shù)是表示一個(gè)“行為”的值

字符串或數(shù)字等常規(guī)值代表 數(shù)據(jù)

函數(shù)可以被視為一個(gè) 行為(action)。

我們可以在變量之間傳遞它們,并在需要時(shí)運(yùn)行。

函數(shù)表達(dá)式 vs 函數(shù)聲明

讓我們來總結(jié)一下函數(shù)聲明和函數(shù)表達(dá)式之間的主要區(qū)別。

首先是語法:如何通過代碼對它們進(jìn)行區(qū)分。

  • 函數(shù)聲明:在主代碼流中聲明為單獨(dú)的語句的函數(shù):
  • // 函數(shù)聲明
    function sum(a, b) {
      return a + b;
    }
  • 函數(shù)表達(dá)式:在一個(gè)表達(dá)式中或另一個(gè)語法結(jié)構(gòu)中創(chuàng)建的函數(shù)。下面這個(gè)函數(shù)是在賦值表達(dá)式 = 右側(cè)創(chuàng)建的:
  • // 函數(shù)表達(dá)式
    let sum = function(a, b) {
      return a + b;
    };

更細(xì)微的差別是,JavaScript 引擎會(huì)在 什么時(shí)候 創(chuàng)建函數(shù)。

函數(shù)表達(dá)式是在代碼執(zhí)行到達(dá)時(shí)被創(chuàng)建,并且僅從那一刻起可用。

一旦代碼執(zhí)行到賦值表達(dá)式 let sum = function… 的右側(cè),此時(shí)就會(huì)開始創(chuàng)建該函數(shù),并且可以從現(xiàn)在開始使用(分配,調(diào)用等)。

函數(shù)聲明則不同。

在函數(shù)聲明被定義之前,它就可以被調(diào)用。

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

這是內(nèi)部算法的原故。當(dāng) JavaScript 準(zhǔn)備 運(yùn)行腳本時(shí),首先會(huì)在腳本中尋找全局函數(shù)聲明,并創(chuàng)建這些函數(shù)。我們可以將其視為“初始化階段”。

在處理完所有函數(shù)聲明后,代碼才被執(zhí)行。所以運(yùn)行時(shí)能夠使用這些函數(shù)。

例如下面的代碼會(huì)正常工作:

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

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

函數(shù)聲明 sayHi 是在 JavaScript 準(zhǔn)備運(yùn)行腳本時(shí)被創(chuàng)建的,在這個(gè)腳本的任何位置都可見。

……如果它是一個(gè)函數(shù)表達(dá)式,它就不會(huì)工作:

sayHi("John"); // error!

let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

函數(shù)表達(dá)式在代碼執(zhí)行到它時(shí)才會(huì)被創(chuàng)建。只會(huì)發(fā)生在 (*) 行。為時(shí)已晚。

函數(shù)聲明的另外一個(gè)特殊的功能是它們的塊級作用域。

嚴(yán)格模式下,當(dāng)一個(gè)函數(shù)聲明在一個(gè)代碼塊內(nèi)時(shí),它在該代碼塊內(nèi)的任何位置都是可見的。但在代碼塊外不可見。

例如,想象一下我們需要依賴于在代碼運(yùn)行過程中獲得的變量 age 聲明一個(gè)函數(shù) welcome()。并且我們計(jì)劃在之后的某個(gè)時(shí)間使用它。

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

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

// 有條件地聲明一個(gè)函數(shù)
if (age < 18) {

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

} else {

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

}

// ……稍后使用
welcome(); // Error: welcome is not defined

這是因?yàn)楹瘮?shù)聲明只在它所在的代碼塊中可見。

下面是另一個(gè)例子:

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

if (age < 18) {
  welcome();               // \   (運(yùn)行)
                           //  |
  function welcome() {     //  |
    alert("Hello!");       //  |  函數(shù)聲明在聲明它的代碼塊內(nèi)任意位置都可用
  }                        //  |
                           //  |
  welcome();               // /   (運(yùn)行)

} else {

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

// 在這里,我們在花括號外部調(diào)用函數(shù),我們看不到它們內(nèi)部的函數(shù)聲明。


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

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

正確的做法是使用函數(shù)表達(dá)式,并將 welcome 賦值給在 if 外聲明的變量,并具有正確的可見性。

下面的代碼可以如愿運(yùn)行:

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

let welcome;

if (age < 18) {

  welcome = function() {
    alert("Hello!");
  };

} else {

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

}

welcome(); // 現(xiàn)在可以了

或者我們可以使用問號運(yùn)算符 ? 來進(jìn)一步對代碼進(jìn)行簡化:

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

let welcome = (age < 18) ?
  function() { alert("Hello!"); } :
  function() { alert("Greetings!"); };

welcome(); // 現(xiàn)在可以了

什么時(shí)候選擇函數(shù)聲明與函數(shù)表達(dá)式?

根據(jù)經(jīng)驗(yàn),當(dāng)我們需要聲明一個(gè)函數(shù)時(shí),首先考慮函數(shù)聲明語法。它能夠?yàn)榻M織代碼提供更多的靈活性。因?yàn)槲覀兛梢栽诼暶鬟@些函數(shù)之前調(diào)用這些函數(shù)。

這對代碼可讀性也更好,因?yàn)樵诖a中查找 function f(…) {…} 比 let f = function(…) {…} 更容易。函數(shù)聲明更“醒目”。

……但是,如果由于某種原因而導(dǎo)致函數(shù)聲明不適合我們(我們剛剛看過上面的例子),那么應(yīng)該使用函數(shù)表達(dá)式。

總結(jié)

  • 函數(shù)是值。它們可以在代碼的任何地方被分配,復(fù)制或聲明。
  • 如果函數(shù)在主代碼流中被聲明為單獨(dú)的語句,則稱為“函數(shù)聲明”。
  • 如果該函數(shù)是作為表達(dá)式的一部分創(chuàng)建的,則稱其“函數(shù)表達(dá)式”。
  • 在執(zhí)行代碼塊之前,內(nèi)部算法會(huì)先處理函數(shù)聲明。所以函數(shù)聲明在其被聲明的代碼塊內(nèi)的任何位置都是可見的。
  • 函數(shù)表達(dá)式在執(zhí)行流程到達(dá)時(shí)創(chuàng)建。

在大多數(shù)情況下,當(dāng)我們需要聲明一個(gè)函數(shù)時(shí),最好使用函數(shù)聲明,因?yàn)楹瘮?shù)在被聲明之前也是可見的。這使我們在代碼組織方面更具靈活性,通常也會(huì)使得代碼可讀性更高。

所以,僅當(dāng)函數(shù)聲明不適合對應(yīng)的任務(wù)時(shí),才應(yīng)使用函數(shù)表達(dá)式。在本章中,我們已經(jīng)看到了幾個(gè)例子,以后還會(huì)看到更多的例子。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號