第三章 函數(shù)
- 函數(shù)是JavaScript中不可或缺的組成部分
- 函數(shù)是構(gòu)造大型程序的工具,可以用于減少重復(fù)性工作、為子程序命名并隔離各個(gè)子程序的運(yùn)行。
3.1 定義函數(shù)
創(chuàng)建函數(shù)的表達(dá)式以關(guān)鍵字function開頭。
var fun = function (parameter1, parameter2, ...) {//參數(shù)可以為空,也可以為一或多個(gè)
// 函數(shù)體,哪怕只有一條語句,也要包含在大括號中,語句會在調(diào)用函數(shù)時(shí)執(zhí)行
...
// return語句決定了函數(shù)的返回值,后面不跟表達(dá)式時(shí)返回undefined
return;
}
3.2 參數(shù)和作用域
函數(shù)的參數(shù)初始值由函數(shù)調(diào)用者提供。
函數(shù)內(nèi)部創(chuàng)建的變量和參數(shù)都屬于函數(shù)的局部變量,這種隔離機(jī)制確保了函數(shù)間不會相互干擾。
3.3 嵌套作用域
可以在函數(shù)中創(chuàng)建其他函數(shù),并產(chǎn)生不同程度的局部作用域。
任何局部作用域都可以訪問到包含它的局部作用域
函數(shù)內(nèi)部變量的可見性取決于函數(shù)在代碼當(dāng)中的位置,在包含了一個(gè)函數(shù)定義的代碼塊中,這個(gè)函數(shù)可以訪問到代碼塊中的所有變量,即函數(shù)上層的代碼塊中的變量和函數(shù)內(nèi)部的變量。這種控制變量的方法稱為詞法作用域。
let關(guān)鍵字作用與var相同,只不過變量作用域是塊作用域而非函數(shù)局部作用域。
3.4 函數(shù)值
函數(shù)和函數(shù)名的區(qū)別:函數(shù)是一種叫做function引用類型的實(shí)例,因此函數(shù)是一個(gè)對象。對象是保存在內(nèi)存中的,函數(shù)名則是指向這個(gè)對象的指針。
JavaScript中函數(shù)是一等公民,可以作為參數(shù)傳入別的函數(shù),也可以作為一個(gè)函數(shù)的返回值,也可以被重新賦值。
3.5 符號聲明
函數(shù)聲明還有一種更為簡潔的方式:
function fun(parameter) {
//todo
}
- 函數(shù)聲明不遵循一般的從上到下的流控制規(guī)則。
console.log('are you ok ?', ans());
function ans() {
return 'yeah, i\'m ok';
}
- 為了確保函數(shù)在不同環(huán)境下運(yùn)行的行為一致,應(yīng)在最外層的函數(shù)或程序作用域中進(jìn)行函數(shù)聲明。
3.6 調(diào)用棧
由于函數(shù)需要在執(zhí)行結(jié)束后跳轉(zhuǎn)回調(diào)用該函數(shù)的代碼位置,因此計(jì)算機(jī)必須記住函數(shù)調(diào)用的上下文。我們將計(jì)算機(jī)存儲這個(gè)上下文的區(qū)域稱之為調(diào)用棧。
當(dāng)函數(shù)調(diào)用時(shí),當(dāng)前的上下文信息就會被存儲在棧頂
當(dāng)函數(shù)返回時(shí),系統(tǒng)會刪除存儲在棧頂?shù)纳舷挛男畔?,并使用該信息繼續(xù)執(zhí)行程序。
// 若計(jì)算機(jī)空間無限大,循環(huán)調(diào)用會一直執(zhí)行下去,但事實(shí)上是該程序會耗盡內(nèi)存空間,導(dǎo)致“??臻g溢出”。
function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + ' came first.');
3.7 可選參數(shù)
JavaScript對傳送函數(shù)的參數(shù)數(shù)量幾乎不做任何限制。如果你傳遞了過多參數(shù),多余的參數(shù)就會被忽略,而如果你傳遞的參數(shù)過少,遺漏的參數(shù)將會被賦值成undefined。
缺點(diǎn):你可能恰好向函數(shù)傳遞了錯誤數(shù)量的參數(shù),但沒有人會告訴你這個(gè)錯誤。
優(yōu)點(diǎn): 我們可以利用這種行為來讓函數(shù)接收可選參數(shù)。
3.8 閉包
如果函數(shù)已經(jīng)執(zhí)行結(jié)束,那么這些由函數(shù)創(chuàng)建的局部變量會如何處理呢?
function wrapValue(n) {
var localVariable = n;
return function { return localVariable; };
}
var wrap1 = wrapValue(1);
var wrap2 = wrapValue(2);
console.log(wrap1());
// 1
console.log(wrap2());
// 2
這段代碼很好地印證了局部變量會在每次函數(shù)調(diào)用時(shí)重新創(chuàng)建,不同的函數(shù)調(diào)用是不會對其他函數(shù)內(nèi)的局部變量產(chǎn)生影響的。
這種引用特定的局部變量實(shí)例的功能稱為閉包。一個(gè)包裝了一些局部變量的函數(shù)是一個(gè)閉包。利用閉包的特性,我們不再需要擔(dān)心變量的生命周期問題,很多高級應(yīng)用都依靠它來實(shí)現(xiàn)。
function multiplier(factor) {
return function(number) {
return number * factor;
}
}
var twice = multiplier(2);
console.log(twice(5));
//10
可以把關(guān)鍵字function當(dāng)做一種“凍結(jié)”代碼并將其打包成函數(shù)值的模型。所以當(dāng)看到“return function(...) {...}”這樣的代碼時(shí),可以將其理解為一個(gè)句柄,其中句柄指向一段包裝好的計(jì)算代碼。
3.9 遞歸
函數(shù)完全可以自己調(diào)用自己,只要避免棧溢出的問題即可。我們把函數(shù)調(diào)用自身的行為稱為遞歸。
function power(base, exponent) {
if (exponent == 0)
return 1;
else
return base * power(base, exponent-1);
}
console.log(power(2, 3));
// 8
需要注意的是,在標(biāo)準(zhǔn)的JavaScript實(shí)現(xiàn)當(dāng)中,遞歸寫法的函數(shù)執(zhí)行效率比循環(huán)寫法的函數(shù)慢了大約10倍。如何權(quán)衡性能與優(yōu)雅是一個(gè)值得考慮的問題,但有一條基本原則:除非程序執(zhí)行速度確實(shí)太慢,否則先不要關(guān)注效率問題。
對于某些問題來說,遞歸相較于循環(huán)更能解決問題。這類問題通常需要執(zhí)行和處理多個(gè)分支,而每個(gè)分支又會引出更多的執(zhí)行分支。
3.10 添加新函數(shù)
兩種常用的引入函數(shù)的方法:
找出程序中多次出現(xiàn)的相似代碼。
寫新功能代碼,覺得一些代碼應(yīng)該包含在一個(gè)函數(shù)時(shí)。甚至可以先編寫調(diào)用函數(shù)的代碼,然后再具體實(shí)現(xiàn)調(diào)用的函數(shù)。
給函數(shù)起名的難易程度取決于我們封裝的函數(shù)的用途是否明確。
3.11 函數(shù)及其副作用
可以將函數(shù)分為兩類:一類調(diào)用后產(chǎn)生副作用,而另一類則產(chǎn)生返回值(當(dāng)然也可以定義同時(shí)產(chǎn)生副作用和返回值的函數(shù))。
相比于直接產(chǎn)生副作用的函數(shù),產(chǎn)生返回值的函數(shù)更容易集成到新的環(huán)境當(dāng)中使用。但在副作用的幫助下,有些操作則更易、更快實(shí)現(xiàn),因此考慮到運(yùn)算速度,有時(shí)候純函數(shù)并不可取。
3.12 本章小結(jié)
- 對于關(guān)鍵字
function來說,當(dāng)我們將其作為表達(dá)式來使用的時(shí)候,可以創(chuàng)建一個(gè)函數(shù)值。當(dāng)我們將其作為語句來使用的時(shí)候,可以用來聲明并將函數(shù)賦予變量。
// Create a function value f
var f = function(a) {
console.log(a + 2);
}
// Declare g to be a function
function g(a, b) {
return a * b * 3.5;
}
要理解函數(shù)的含義,就必須理解局部作用域的概念。對于一個(gè)函數(shù)來說,其參數(shù)及其內(nèi)部聲明的變量都是局部變量,每當(dāng)調(diào)用函數(shù)時(shí),這些變量都會被重新創(chuàng)建,而且對外并不可見。而在函數(shù)作用域當(dāng)中聲明的函數(shù),可以訪問其外部函數(shù)的局部作用域。
將程序中的任務(wù)劃分到不同的函數(shù)中的做法是非常有用的,而且有助于提高代碼的可讀性。