概念
函數(shù)(function)樟蠕,也叫作功能贮聂、方法,函數(shù)可以將一段代碼一起封裝起來寨辩,被封裝起來的函數(shù)具備某一項特殊的功能吓懈,內(nèi)部封裝的一段代碼作為一個完整的結(jié)構(gòu)體,要執(zhí)行就都執(zhí)行靡狞,要不執(zhí)行就都不執(zhí)行耻警。函數(shù)的作用就是封裝一段代碼,將來可以重復(fù)使用甸怕。
聲明
函數(shù)聲明又叫函數(shù)定義甘穿,函數(shù)必須先定義然后才能使用。如果沒有定義函數(shù)直接使用梢杭,會出現(xiàn)一個引用錯誤扒磁。函數(shù)聲明語法:
function 函數(shù)名(參數(shù)){
封裝的結(jié)構(gòu)體;
}
特點(diǎn):函數(shù)聲明的時候式曲,函數(shù)體并不會執(zhí)行妨托,只有當(dāng)函數(shù)被調(diào)用的時候才會執(zhí)行。
調(diào)用
調(diào)用方法:函數(shù)名();
函數(shù)調(diào)用也叫作函數(shù)執(zhí)行吝羞,調(diào)用時會將函數(shù)內(nèi)部封裝的所有的結(jié)構(gòu)體的代碼立即執(zhí)行兰伤。
函數(shù)內(nèi)部語句執(zhí)行的位置,與函數(shù)定義的位置無關(guān)钧排,與函數(shù)調(diào)用位置有關(guān)敦腔。
函數(shù)可以一次定義,多次執(zhí)行恨溜。
參數(shù)
函數(shù)預(yù)留了一個接口符衔,專門用于讓用戶自定義內(nèi)容,使函數(shù)發(fā)生一些執(zhí)行效果變化糟袁。
- 接口:就是函數(shù)的參數(shù)判族,函數(shù)參數(shù)的本質(zhì)就是變量,可以接收任意類型的數(shù)據(jù)项戴,導(dǎo)致函數(shù)執(zhí)行結(jié)果根據(jù)參數(shù)不同形帮,結(jié)果也不同。
一個函數(shù)可以設(shè)置 0 個或者多個參數(shù)周叮,參數(shù)之間用逗號分隔辩撑。
函數(shù)的參數(shù)根據(jù)書寫位置不同,名稱也不同:
- 形式參數(shù):定義的 () 內(nèi)部的參數(shù)仿耽,叫做形式參數(shù)合冀,本質(zhì)是變量,可以接收實(shí)際參數(shù)傳遞過來的數(shù)據(jù)项贺。簡稱形參君躺。
- 實(shí)際參數(shù):調(diào)用的 () 內(nèi)部的參數(shù)峭判,叫做實(shí)際參數(shù),本質(zhì)就是傳遞的各種類型的數(shù)據(jù)晰洒,傳遞給每個形參,簡稱實(shí)參啥箭。
函數(shù)執(zhí)行過程谍珊,伴隨著傳參的過程:
函數(shù)參數(shù)的優(yōu)點(diǎn): - 不論使用自己封裝的函數(shù),還是其他人封裝的函數(shù)急侥,只需要知道傳遞什么參數(shù)砌滞,執(zhí)行什么功能,沒必要知道內(nèi)部的結(jié)構(gòu)什么坏怪。
- 一般自己封裝的函數(shù)或者其他人封裝的函數(shù)需要有一個 API 接口說明贝润,告訴用戶參數(shù)需要傳遞什么類型的數(shù)據(jù),實(shí)現(xiàn)什么功能铝宵。
返回值
函數(shù)能夠通過參數(shù)接收數(shù)據(jù)打掘,也能夠?qū)⒑瘮?shù)執(zhí)行結(jié)果返回一個值∨羟铮可以利用函數(shù)內(nèi)部的一個 return 的關(guān)鍵字設(shè)置函數(shù)的返回值尊蚁。
- 作用 ①:函數(shù)內(nèi)部如果結(jié)構(gòu)體執(zhí)行到一個 return 的關(guān)鍵字,會立即停止后面代碼的執(zhí)行侣夷。
- 作用 ②:可以在 return 關(guān)鍵字后面添加空格横朋,空格后面任意定義一個數(shù)據(jù)字面量或者表達(dá)式,函數(shù)在執(zhí)行完自身功能之后百拓,整體會被 return 矮化成一個表達(dá)式琴锭,表達(dá)式必須求出一個值繼續(xù)可以參與程序,表達(dá)式的值就是 return 后面的數(shù)據(jù)衙传。
函數(shù)如果有返回值决帖,執(zhí)行結(jié)果可以當(dāng)成普通數(shù)據(jù)參與程序,也可以作為一個普通數(shù)據(jù)賦值給一個變量蓖捶,甚至賦值給其他函數(shù)的實(shí)際參數(shù)古瓤。
注意:如果函數(shù)沒有設(shè)置 return 語句 ,那么函數(shù)有默認(rèn)的返回值 undefined腺阳;如果函數(shù)使用 return 語句落君,但是 return 后面沒有任何值,那么函數(shù)的返回值也是 undefined亭引。
表達(dá)式
函數(shù)表達(dá)式是函數(shù)定義的另外一種方式绎速。定義方法就是將函數(shù)的定義、匿名函數(shù)賦值給一個變量焙蚓。函數(shù)定義賦值給一個變量纹冤,相當(dāng)于將函數(shù)整體矮化成了一個表達(dá)式洒宝。匿名函數(shù)的意思是函數(shù)沒有函數(shù)名。調(diào)用函數(shù)表達(dá)式萌京,方法是給變量名加()執(zhí)行雁歌,不能使用函數(shù)名加()執(zhí)行。
數(shù)據(jù)類型
函數(shù)是一種單獨(dú)的數(shù)據(jù)類型 Function知残。由于函數(shù)是一種數(shù)據(jù)類型靠瞎,可以參與其他程序。例如求妹,可以把函數(shù)作為另一個函數(shù)的參數(shù)乏盐,在另一個函數(shù)中調(diào)用,或者制恍,可以把函數(shù)可以作為返回值從函數(shù)內(nèi)部返回父能。
arguments對象
JavaScript 中,arguments 對象是比較特別的一個對象净神,實(shí)際上是當(dāng)前函數(shù)的一個內(nèi)置屬性何吝。也就是說所有函數(shù)都內(nèi)置了一個 arguments 對象,arguments 對象中存儲了傳遞的所有的實(shí)參鹃唯。arguments 是一個偽數(shù)組岔霸,因此及可以進(jìn)行遍歷。
函數(shù)的實(shí)參個數(shù)和形參個數(shù)可以不一致俯渤,所有的實(shí)參都會存儲在函數(shù)內(nèi)部的 arguments 類數(shù)組對象中呆细。
function fun() {
console.log(arguments);
}
遞歸
函數(shù)內(nèi)部可以通過函數(shù)名調(diào)用函數(shù)自身的方式,就是函數(shù)遞歸現(xiàn)象八匠。遞歸的次數(shù)太多容易出現(xiàn)錯誤:超出計算機(jī)的計算最大能力絮爷。更多時候,使用遞歸去解決一些數(shù)學(xué)中的現(xiàn)象梨树。例如可以輸出斐波那契數(shù)列的某一項的值坑夯。
// 菲波那切數(shù)列
// 參數(shù):正整數(shù)
// 返回值:對應(yīng)的整數(shù)位置的菲波那切數(shù)列的值
function fibo(a) {
if (a === 1 || a === 2) {
return 1;
} else {
return fibo(a - 1) + fibo(a - 2);
}
}
作用域
作用域指變量可以起作用的范圍。如果變量定義在一個函數(shù)內(nèi)部抡四,只能在函數(shù)內(nèi)部被訪問到柜蜈,在函數(shù)外部不能使用這個變量,函數(shù)就是變量定義的作用域指巡。任何一對花括號 {} 中的結(jié)構(gòu)體都屬于一個塊淑履,在這之中定義的所有變量在代碼塊外都是不可見的,我們稱之為塊級作用域藻雪。在es6之前沒有塊級作用域的的概念,只有函數(shù)作用域秘噪。
全局變量和局部變量
- 局部變量:定義在函數(shù)內(nèi)部的變量,只能在函數(shù)作用域內(nèi)部被訪問到勉耀,在外面沒有定義的指煎。
- 全局變量:從廣義上來說蹋偏,也是一種局部變量,定義在全局的變量至壤,作用域范圍是全局威始,在整個 js 程序任意位置都能夠被訪問到。
變量退出作用域之后會銷毀像街,全局變量關(guān)閉網(wǎng)頁或瀏覽器才會銷毀黎棠。
函數(shù)的參數(shù)本質(zhì)是一個變量,也有自己的作用域宅广,函數(shù)的參數(shù)也是屬于函數(shù)自己內(nèi)部的局部變量葫掉,只能在函數(shù)內(nèi)部被使用些举,在函數(shù)外面沒有定義跟狱。
函數(shù)也有自己的作用域,定義在哪個作用域內(nèi)部户魏,只能在這個作用域范圍內(nèi)被訪問驶臊,出了作用域不能被訪問的。函數(shù)定義在另一個函數(shù)內(nèi)部叼丑,如果外部函數(shù)沒有執(zhí)行時关翎,相當(dāng)于內(nèi)部代碼沒寫。
作用域鏈
只有函數(shù)可以制造作用域結(jié)構(gòu)鸠信, 那么只要是代碼纵寝,就至少有一個作用域, 即全局作用域。凡是代碼中有函數(shù)星立,那么這個函數(shù)就構(gòu)成另一個作用域爽茴。如果函數(shù)中還有函數(shù),那么在這個作用域中就又可以誕生一個作用域绰垂。
將這樣的所有的作用域列出來室奏,可以有一個結(jié)構(gòu): 函數(shù)內(nèi)指向函數(shù)外的鏈?zhǔn)浇Y(jié)構(gòu)。就稱作作用域鏈劲装。
function f1() {
function f2() {
}
}
var num = 456;
function f3() {
function f4() {
}
}
function f1() {
var num = 123;
function f2() {
console.log(num);
}
f2();
}
var num = 456;
f1();
遮蔽效應(yīng)
程序在遇到一個變量時胧沫,使用時作用域查找順序,不同層次的函數(shù)內(nèi)都有可能定義相同名字的變量占业,一個變量在使用時绒怨,會優(yōu)先從自己所在層作用域查找變量,如果當(dāng)前層沒有變量定義會按照順序從本層往外依次查找谦疾,直到找到第一個變量定義窖逗。整個過程中會發(fā)生內(nèi)層變量遮蔽外層變量的效果,叫做“遮蔽效應(yīng)”餐蔬。
不寫 var 關(guān)鍵字的影響:在函數(shù)內(nèi)部想要定義新的變量碎紊,如果不加關(guān)鍵字 var佑附,相當(dāng)于定義的全局變量。如果全局也有相同的標(biāo)識符仗考,會被函數(shù)內(nèi)部的變量影響音同,局部變量污染全局變量。所以每次定義變量時都必須寫 var 關(guān)鍵字秃嗜,否則就會定義在全局权均,可能污染全局。
預(yù)解析與聲明提升
預(yù)解析
JavaScript 代碼的執(zhí)行是由瀏覽器中的 JavaScript 解析器來執(zhí)行的锅锨。JavaScript 解析器執(zhí)行 JavaScript 代碼的時候叽赊,分為兩個過程:預(yù)解析過程和代碼執(zhí)行過程。
預(yù)解析過程:
- 把變量的聲明提升到當(dāng)前作用域的最前面必搞,只會提升聲明必指,不會提升賦值。
- 把函數(shù)的聲明提升到當(dāng)前作用域的最前面恕洲,只會提升聲明塔橡,不會提升調(diào)用。
- 先提升 var霜第,再提升 function葛家。
JavaScript 的執(zhí)行過程:在預(yù)解析之后,根據(jù)新的代碼順序泌类,從上往下按照既定規(guī)律執(zhí)行 js 代碼癞谒。
變量聲明提升
在預(yù)解析過程中,所有定義的變量刃榨,都會將聲明的過程提升到所在的作用域最上面弹砚,在將來的代碼執(zhí)行過程中,按照先后順序會先執(zhí)行被提升的聲明變量過程喇澡。
提升過程中迅栅,只提升聲明過程,不提升變量賦值晴玖,相當(dāng)于變量定義未賦值读存,變量內(nèi)存儲 undefined 值。
因此呕屎,在 js 中會出現(xiàn)一種現(xiàn)象让簿,在前面調(diào)用后定義的變量,不會報錯秀睛,只會使用 undefined 值尔当。
函數(shù)聲明提升
在預(yù)解析過程中,所有定義的函數(shù),都會將聲明的過程提升到所在的作用域最上面椭迎,在將來的代碼執(zhí)行過程中锐帜,按照先后順序會先執(zhí)行被提升的函數(shù)聲明過程。
在預(yù)解析之后的代碼執(zhí)行過程中畜号,函數(shù)定義過程已經(jīng)在最開始就會執(zhí)行缴阎,一旦函數(shù)定義成功,后續(xù)就可以直接調(diào)用函數(shù)简软。
因此蛮拔,在 js 中會出現(xiàn)一種現(xiàn)象,在前面調(diào)用后定義的函數(shù)痹升,不會報錯建炫,而且能正常執(zhí)行函數(shù)內(nèi)部的代碼。
提升順序
預(yù)解析過程中疼蛾,先提升 var 變量聲明肛跌,再提升 function 函數(shù)聲明。
假設(shè)出現(xiàn)變量名和函數(shù)名相同据过,那么后提升的函數(shù)名標(biāo)識符會覆蓋先提升的變量名惋砂,那么在后續(xù)代碼中出現(xiàn)調(diào)用標(biāo)識符時妒挎,內(nèi)部是函數(shù)的定義過程绳锅,而不是 undefined。
如果調(diào)用標(biāo)識符的過程在源代碼函數(shù)和變量定義后面酝掩,相當(dāng)于函數(shù)名覆蓋了一次變量名鳞芙,結(jié)果在執(zhí)行到變量賦值時,又被新值覆蓋了函數(shù)的值期虾,那么在后面再次調(diào)用標(biāo)識符原朝,用的就是變量存的新值。
建議:不要書寫相同的標(biāo)識符給變量名或函數(shù)名镶苞,避免出現(xiàn)覆蓋喳坠。
函數(shù)表達(dá)式的提升
在預(yù)解析過程中,函數(shù)表達(dá)式進(jìn)行的是變量聲明提升茂蚓,而不是函數(shù)聲明提升壕鹉。提升后變量內(nèi)部存的是一個 undefined。在前面進(jìn)行函數(shù)方法調(diào)用聋涨,數(shù)據(jù)類型會提示錯誤晾浴。
建議:定義函數(shù)時,最好使用 function 關(guān)鍵字定義方式牍白,這樣函數(shù)聲明提升可以永遠(yuǎn)生效脊凰。
函數(shù)聲明提升可以用于調(diào)整代碼的順序,將大段的定義過程放到代碼最后茂腥,但是不影響代碼執(zhí)行效果狸涌。
IIFE 自調(diào)用函數(shù)
IIFE:immediately-invoked function expression切省,叫做即時調(diào)用的函數(shù)表達(dá)式,也叫做自調(diào)用函數(shù)帕胆,表示函數(shù)在定義時就立即調(diào)用数尿。
函數(shù)調(diào)用方式:函數(shù)名或函數(shù)表達(dá)式的變量名后面加 () 運(yùn)算符。
函數(shù)名定義的形式不能實(shí)現(xiàn)立即執(zhí)行自調(diào)用惶楼,函數(shù)使用函數(shù)表達(dá)式形式可以實(shí)現(xiàn)立即執(zhí)行右蹦,原因是因為函數(shù)表達(dá)式定義過程中,將一個函數(shù)矮化成了一個表達(dá)式歼捐,后面加()運(yùn)算符就可以立即執(zhí)行何陆。
如果想實(shí)現(xiàn) IIFE,可以想辦法將函數(shù)矮化成表達(dá)式豹储。
函數(shù)矮化成表達(dá)式的方法贷盲,可以讓函數(shù)參與一些運(yùn)算,也就是說給函數(shù)前面加一些運(yùn)算符剥扣。
- 數(shù)學(xué)運(yùn)算符:+ - ()
- 邏輯運(yùn)算符:巩剖!非運(yùn)算
IIFE 結(jié)構(gòu)可以關(guān)住函數(shù)的作用域,在結(jié)構(gòu)外面是不能調(diào)用函數(shù)的钠怯。
IIFE 最常用的是 () 運(yùn)算符佳魔,而且函數(shù)可以不寫函數(shù)名,使用匿名函數(shù)晦炊。