第三章 函數(shù)
- 函數(shù)是JavaScript中不可或缺的組成部分
- 函數(shù)是構(gòu)造大型程序的工具,可以用于減少重復(fù)性工作羡儿、為子程序命名并隔離各個(gè)子程序的運(yùn)行礼患。
3.1 定義函數(shù)
創(chuàng)建函數(shù)的表達(dá)式以關(guān)鍵字function
開(kāi)頭。
var fun = function (parameter1, parameter2, ...) {//參數(shù)可以為空掠归,也可以為一或多個(gè)
// 函數(shù)體缅叠,哪怕只有一條語(yǔ)句,也要包含在大括號(hào)中虏冻,語(yǔ)句會(huì)在調(diào)用函數(shù)時(shí)執(zhí)行
...
// return語(yǔ)句決定了函數(shù)的返回值肤粱,后面不跟表達(dá)式時(shí)返回undefined
return;
}
3.2 參數(shù)和作用域
函數(shù)的參數(shù)初始值由函數(shù)調(diào)用者提供。
函數(shù)內(nèi)部創(chuàng)建的變量和參數(shù)都屬于函數(shù)的局部變量厨相,這種隔離機(jī)制確保了函數(shù)間不會(huì)相互干擾领曼。
3.3 嵌套作用域
可以在函數(shù)中創(chuàng)建其他函數(shù),并產(chǎn)生不同程度的局部作用域蛮穿。
任何局部作用域都可以訪問(wèn)到包含它的局部作用域
函數(shù)內(nèi)部變量的可見(jiàn)性取決于函數(shù)在代碼當(dāng)中的位置庶骄,在包含了一個(gè)函數(shù)定義的代碼塊中,這個(gè)函數(shù)可以訪問(wèn)到代碼塊中的所有變量践磅,即函數(shù)上層的代碼塊中的變量和函數(shù)內(nèi)部的變量单刁。這種控制變量的方法稱為詞法作用域。
let
關(guān)鍵字作用與var
相同府适,只不過(guò)變量作用域是塊作用域而非函數(shù)局部作用域羔飞。
3.4 函數(shù)值
函數(shù)和函數(shù)名的區(qū)別:函數(shù)是一種叫做function引用類型的實(shí)例肺樟,因此函數(shù)是一個(gè)對(duì)象。對(duì)象是保存在內(nèi)存中的褥傍,函數(shù)名則是指向這個(gè)對(duì)象的指針儡嘶。
JavaScript中函數(shù)是一等公民喇聊,可以作為參數(shù)傳入別的函數(shù)恍风,也可以作為一個(gè)函數(shù)的返回值,也可以被重新賦值誓篱。
3.5 符號(hào)聲明
函數(shù)聲明還有一種更為簡(jiǎn)潔的方式:
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ī)存儲(chǔ)這個(gè)上下文的區(qū)域稱之為調(diào)用棧
邻遏。
當(dāng)函數(shù)調(diào)用時(shí)糠亩,當(dāng)前的上下文信息就會(huì)被存儲(chǔ)在棧頂
當(dāng)函數(shù)返回時(shí),系統(tǒng)會(huì)刪除存儲(chǔ)在棧頂?shù)纳舷挛男畔⒆佳椋⑹褂迷撔畔⒗^續(xù)執(zhí)行程序赎线。
// 若計(jì)算機(jī)空間無(wú)限大,循環(huán)調(diào)用會(huì)一直執(zhí)行下去糊饱,但事實(shí)上是該程序會(huì)耗盡內(nèi)存空間垂寥,導(dǎo)致“棧空間溢出”另锋。
function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + ' came first.');
3.7 可選參數(shù)
JavaScript對(duì)傳送函數(shù)的參數(shù)數(shù)量幾乎不做任何限制滞项。如果你傳遞了過(guò)多參數(shù),多余的參數(shù)就會(huì)被忽略夭坪,而如果你傳遞的參數(shù)過(guò)少文判,遺漏的參數(shù)將會(huì)被賦值成undefined。
缺點(diǎn):你可能恰好向函數(shù)傳遞了錯(cuò)誤數(shù)量的參數(shù)室梅,但沒(méi)有人會(huì)告訴你這個(gè)錯(cuò)誤律杠。
優(yōu)點(diǎn): 我們可以利用這種行為來(lái)讓函數(shù)接收可選參數(shù)。
3.8 閉包
如果函數(shù)已經(jīng)執(zhí)行結(jié)束竞惋,那么這些由函數(shù)創(chuàng)建的局部變量會(huì)如何處理呢柜去?
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
這段代碼很好地印證了局部變量會(huì)在每次函數(shù)調(diào)用時(shí)重新創(chuàng)建,不同的函數(shù)調(diào)用是不會(huì)對(duì)其他函數(shù)內(nèi)的局部變量產(chǎn)生影響的拆宛。
這種引用特定的局部變量實(shí)例的功能稱為閉包嗓奢。一個(gè)包裝了一些局部變量的函數(shù)是一個(gè)閉包。利用閉包的特性浑厚,我們不再需要擔(dān)心變量的生命周期問(wèn)題股耽,很多高級(jí)應(yīng)用都依靠它來(lái)實(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)用自己诬乞,只要避免棧溢出的問(wèn)題即可册赛。我們把函數(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)中森瘪,遞歸寫(xiě)法的函數(shù)執(zhí)行效率比循環(huán)寫(xiě)法的函數(shù)慢了大約10倍。如何權(quán)衡性能與優(yōu)雅是一個(gè)值得考慮的問(wèn)題票堵,但有一條基本原則:除非程序執(zhí)行速度確實(shí)太慢扼睬,否則先不要關(guān)注效率問(wèn)題。
對(duì)于某些問(wèn)題來(lái)說(shuō)悴势,遞歸相較于循環(huán)更能解決問(wèn)題窗宇。這類問(wèn)題通常需要執(zhí)行和處理多個(gè)分支,而每個(gè)分支又會(huì)引出更多的執(zhí)行分支特纤。
3.10 添加新函數(shù)
兩種常用的引入函數(shù)的方法:
找出程序中多次出現(xiàn)的相似代碼质蕉。
寫(xiě)新功能代碼橱野,覺(jué)得一些代碼應(yīng)該包含在一個(gè)函數(shù)時(shí)媒抠。甚至可以先編寫(xiě)調(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é)
- 對(duì)于關(guān)鍵字
function
來(lái)說(shuō)扇雕,當(dāng)我們將其作為表達(dá)式來(lái)使用的時(shí)候,可以創(chuàng)建一個(gè)函數(shù)值窥摄。當(dāng)我們將其作為語(yǔ)句來(lái)使用的時(shí)候镶奉,可以用來(lái)聲明并將函數(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ù)的含義,就必須理解局部作用域的概念哨苛。對(duì)于一個(gè)函數(shù)來(lái)說(shuō)鸽凶,其參數(shù)及其內(nèi)部聲明的變量都是局部變量,每當(dāng)調(diào)用函數(shù)時(shí)建峭,這些變量都會(huì)被重新創(chuàng)建玻侥,而且對(duì)外并不可見(jiàn)。而在函數(shù)作用域當(dāng)中聲明的函數(shù)亿蒸,可以訪問(wèn)其外部函數(shù)的局部作用域凑兰。
將程序中的任務(wù)劃分到不同的函數(shù)中的做法是非常有用的,而且有助于提高代碼的可讀性祝懂。