W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
在 JavaScript 中,函數(shù)不是“神奇的語言結構”,而是一種特殊的值。
我們在前面章節(jié)使用的語法稱為 函數(shù)聲明:
function sayHi() {
alert( "Hello" );
}
另一種創(chuàng)建函數(shù)的語法稱為 函數(shù)表達式。
它允許我們在任何表達式的中間創(chuàng)建一個新函數(shù)。
例如:
let sayHi = function() {
alert( "Hello" );
};
在這里我們可以看到變量 sayHi
得到了一個值,新函數(shù) function() { alert("Hello"); }
。
由于函數(shù)創(chuàng)建發(fā)生在賦值表達式的上下文中(在 =
的右側(cè)),因此這是一個 函數(shù)表達式。
請注意,function
關鍵字后面沒有函數(shù)名。函數(shù)表達式允許省略函數(shù)名。
這里我們立即將它賦值給變量,所以上面的兩個代碼示例的含義是一樣的:“創(chuàng)建一個函數(shù)并將其放入變量 sayHi
中”。
在更多更高階的情況下,稍后我們會遇到,可以創(chuàng)建一個函數(shù)并立即調(diào)用,或者安排稍后執(zhí)行,而不是存儲在任何地方,因此保持匿名。
重申一次:無論函數(shù)是如何創(chuàng)建的,函數(shù)都是一個值。上面的兩個示例都在 sayHi
變量中存儲了一個函數(shù)。
我們還可以用 alert
顯示這個變量的值:
function sayHi() {
alert( "Hello" );
}
alert( sayHi ); // 顯示函數(shù)代碼
注意,最后一行代碼并不會運行函數(shù),因為 sayHi
后沒有括號。在某些編程語言中,只要提到函數(shù)的名稱都會導致函數(shù)的調(diào)用執(zhí)行,但 JavaScript 可不是這樣。
在 JavaScript 中,函數(shù)是一個值,所以我們可以把它當成值對待。上面代碼顯示了一段字符串值,即函數(shù)的源碼。
的確,在某種意義上說一個函數(shù)是一個特殊值,我們可以像 sayHi()
這樣調(diào)用它。
但它依然是一個值,所以我們可以像使用其他類型的值一樣使用它。
我們可以復制函數(shù)到其他變量:
function sayHi() { // (1) 創(chuàng)建
alert( "Hello" );
}
let func = sayHi; // (2) 復制
func(); // Hello // (3) 運行復制的值(正常運行)!
sayHi(); // Hello // 這里也能運行(為什么不行呢)
解釋一下上段代碼發(fā)生的細節(jié):
(1)
? 行聲明創(chuàng)建了函數(shù),并把它放入到變量 ?sayHi
?。(2)
? 行將 ?sayHi
?復制到了變量 ?func
?。請注意:?sayHi
?后面沒有括號。如果有括號,?func = sayHi()
? 會把 ?sayHi()
? 的調(diào)用結果寫進?func
?,而不是 ?sayHi
?函數(shù) 本身。sayHi()
? 和 ?func()
? 兩種方式進行調(diào)用。我們也可以在第一行中使用函數(shù)表達式來聲明 ?sayHi
?:
let sayHi = function() { // (1) 創(chuàng)建
alert( "Hello" );
};
let func = sayHi;
// ...
這兩種聲明的函數(shù)是一樣的。
為什么這里末尾會有個分號?
你可能想知道,為什么函數(shù)表達式結尾有一個分號
;
,而函數(shù)聲明沒有:
function sayHi() { // ... } let sayHi = function() { // ... };
答案很簡單:這里函數(shù)表達式是在賦值語句
let sayHi = ...;
中以function(…) {…}
的形式創(chuàng)建的。建議在語句末尾加上分號;
,它不是函數(shù)語法的一部分。
分號用于更簡單的賦值,例如
let sayHi = 5;
,它也用于函數(shù)賦值。
讓我們多舉幾個例子,看看如何將函數(shù)作為值來傳遞以及如何使用函數(shù)表達式。
我們寫一個包含三個參數(shù)的函數(shù) ask(question, yes, no)
:
?question
?
關于問題的文本
?yes
?
當回答為 “Yes” 時,要運行的腳本
?no
?
當回答為 “No” 時,要運行的腳本
函數(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);
在實際開發(fā)中,這樣的函數(shù)是非常有用的。實際開發(fā)與上述示例最大的區(qū)別是,實際開發(fā)中的函數(shù)會通過更加復雜的方式與用戶進行交互,而不是通過簡單的 confirm
。在瀏覽器中,這樣的函數(shù)通常會繪制一個漂亮的提問窗口。但這是另外一件事了。
ask
的兩個參數(shù)值 showOk
和 showCancel
可以被稱為 回調(diào)函數(shù) 或簡稱 回調(diào)。
主要思想是我們傳遞一個函數(shù),并期望在稍后必要時將其“回調(diào)”。在我們的例子中,showOk
是回答 “yes” 的回調(diào),showCancel
是回答 “no” 的回調(diào)。
我們可以使用函數(shù)表達式來編寫一個等價的、更簡潔的函數(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)進行函數(shù)聲明。這兩個函數(shù)沒有名字,所以叫 匿名函數(shù)。這樣的函數(shù)在 ask
外無法訪問(因為沒有對它們分配變量),不過這正是我們想要的。
這樣的代碼在我們的腳本中非常常見,這正符合 JavaScript 語言的思想。
一個函數(shù)是表示一個“行為”的值
字符串或數(shù)字等常規(guī)值代表 數(shù)據(jù)。
函數(shù)可以被視為一個 行為(action)。
我們可以在變量之間傳遞它們,并在需要時運行。
讓我們來總結一下函數(shù)聲明和函數(shù)表達式之間的主要區(qū)別。
首先是語法:如何通過代碼對它們進行區(qū)分。
// 函數(shù)聲明
function sum(a, b) {
return a + b;
}
=
右側(cè)創(chuàng)建的:// 函數(shù)表達式
let sum = function(a, b) {
return a + b;
};
更細微的差別是,JavaScript 引擎會在 什么時候 創(chuàng)建函數(shù)。
函數(shù)表達式是在代碼執(zhí)行到達時被創(chuàng)建,并且僅從那一刻起可用。
一旦代碼執(zhí)行到賦值表達式 let sum = function…
的右側(cè),此時就會開始創(chuàng)建該函數(shù),并且可以從現(xiàn)在開始使用(分配,調(diào)用等)。
函數(shù)聲明則不同。
在函數(shù)聲明被定義之前,它就可以被調(diào)用。
例如,一個全局函數(shù)聲明對整個腳本來說都是可見的,無論它被寫在這個腳本的哪個位置。
這是內(nèi)部算法的原故。當 JavaScript 準備 運行腳本時,首先會在腳本中尋找全局函數(shù)聲明,并創(chuàng)建這些函數(shù)。我們可以將其視為“初始化階段”。
在處理完所有函數(shù)聲明后,代碼才被執(zhí)行。所以運行時能夠使用這些函數(shù)。
例如下面的代碼會正常工作:
sayHi("John"); // Hello, John
function sayHi(name) {
alert( `Hello, ${name}` );
}
函數(shù)聲明 sayHi
是在 JavaScript 準備運行腳本時被創(chuàng)建的,在這個腳本的任何位置都可見。
……如果它是一個函數(shù)表達式,它就不會工作:
sayHi("John"); // error!
let sayHi = function(name) { // (*) no magic any more
alert( `Hello, ${name}` );
};
函數(shù)表達式在代碼執(zhí)行到它時才會被創(chuàng)建。只會發(fā)生在 (*)
行。為時已晚。
函數(shù)聲明的另外一個特殊的功能是它們的塊級作用域。
嚴格模式下,當一個函數(shù)聲明在一個代碼塊內(nèi)時,它在該代碼塊內(nèi)的任何位置都是可見的。但在代碼塊外不可見。
例如,想象一下我們需要依賴于在代碼運行過程中獲得的變量 age
聲明一個函數(shù) welcome()
。并且我們計劃在之后的某個時間使用它。
如果我們使用函數(shù)聲明,則以下代碼無法像預期那樣工作:
let age = prompt("What is your age?", 18);
// 有條件地聲明一個函數(shù)
if (age < 18) {
function welcome() {
alert("Hello!");
}
} else {
function welcome() {
alert("Greetings!");
}
}
// ……稍后使用
welcome(); // Error: welcome is not defined
這是因為函數(shù)聲明只在它所在的代碼塊中可見。
下面是另一個例子:
let age = 16; // 拿 16 作為例子
if (age < 18) {
welcome(); // \ (運行)
// |
function welcome() { // |
alert("Hello!"); // | 函數(shù)聲明在聲明它的代碼塊內(nèi)任意位置都可用
} // |
// |
welcome(); // / (運行)
} else {
function welcome() {
alert("Greetings!");
}
}
// 在這里,我們在花括號外部調(diào)用函數(shù),我們看不到它們內(nèi)部的函數(shù)聲明。
welcome(); // Error: welcome is not defined
我們怎么才能讓 welcome
在 if
外可見呢?
正確的做法是使用函數(shù)表達式,并將 welcome
賦值給在 if
外聲明的變量,并具有正確的可見性。
下面的代碼可以如愿運行:
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)在可以了
或者我們可以使用問號運算符 ?
來進一步對代碼進行簡化:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
function() { alert("Hello!"); } :
function() { alert("Greetings!"); };
welcome(); // 現(xiàn)在可以了
什么時候選擇函數(shù)聲明與函數(shù)表達式?
根據(jù)經(jīng)驗,當我們需要聲明一個函數(shù)時,首先考慮函數(shù)聲明語法。它能夠為組織代碼提供更多的靈活性。因為我們可以在聲明這些函數(shù)之前調(diào)用這些函數(shù)。
這對代碼可讀性也更好,因為在代碼中查找
function f(…) {…}
比let f = function(…) {…}
更容易。函數(shù)聲明更“醒目”。
……但是,如果由于某種原因而導致函數(shù)聲明不適合我們(我們剛剛看過上面的例子),那么應該使用函數(shù)表達式。
在大多數(shù)情況下,當我們需要聲明一個函數(shù)時,最好使用函數(shù)聲明,因為函數(shù)在被聲明之前也是可見的。這使我們在代碼組織方面更具靈活性,通常也會使得代碼可讀性更高。
所以,僅當函數(shù)聲明不適合對應的任務時,才應使用函數(shù)表達式。在本章中,我們已經(jīng)看到了幾個例子,以后還會看到更多的例子。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: