W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
在 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ù)是如何創(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)
? 行聲明創(chuàng)建了函數(shù),并把它放入到變量 ?sayHi
?。(2)
? 行將 ?sayHi
?復(fù)制到了變量 ?func
?。請注意:?sayHi
?后面沒有括號。如果有括號,?func = sayHi()
? 會(huì)把 ?sayHi()
? 的調(diào)用結(jié)果寫進(jìn)?func
?,而不是 ?sayHi
?函數(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ù)賦值。
讓我們多舉幾個(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)行。
讓我們來總結(jié)一下函數(shù)聲明和函數(shù)表達(dá)式之間的主要區(qū)別。
首先是語法:如何通過代碼對它們進(jìn)行區(qū)分。
// 函數(shù)聲明
function sum(a, b) {
return a + b;
}
=
右側(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á)式。
在大多數(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ì)看到更多的例子。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: