在 JS 中吁恍,函數(shù)就是一個(gè)方法,一般都是為了實(shí)現(xiàn)某個(gè)功能。
1. 函數(shù)的作用和創(chuàng)建
var total = 10;
total += 10;
total = total/2;
total = total.toFixed(2); //=> 保留小數(shù)點(diǎn)后面兩位蹬挤,toFixed 時(shí)候數(shù)字包裝對(duì)象的方法缚窿,用來保留小數(shù)點(diǎn)后面的位數(shù)
在后續(xù)的代碼中,依然想實(shí)現(xiàn)相同的操作焰扳,就需要重新編寫代碼倦零。這樣的方式會(huì)導(dǎo)致頁面中存在大量冗余的代碼,也降低了開發(fā)效率吨悍。
函數(shù)的誕生的目的就是為了實(shí)現(xiàn)封裝:把實(shí)現(xiàn)一個(gè)功能的代碼封裝到一個(gè)函數(shù)中扫茅,以便后期重復(fù)利用。起到了低耦合高內(nèi)聚(減少頁面中的冗余代碼育瓜,提高代碼的重復(fù)利用率)的作用
創(chuàng)建函數(shù)
// ES5 中:
function 函數(shù)名([參數(shù)]) {
// 函數(shù)體
}
// 表達(dá)式創(chuàng)建
var 函數(shù)名 = function ([參數(shù)]) {
// 函數(shù)體
}
// ES6 箭頭函數(shù)
let 函數(shù)名或者說變量名 = ([參數(shù)]) => {
// 函數(shù)體
}
2. 函數(shù)的創(chuàng)建執(zhí)行機(jī)制
函數(shù)作為引用類型葫隙,也是按照引用地址來操作的。
【創(chuàng)建函數(shù)】
- 首先開辟一個(gè)新的堆內(nèi)存躏仇,把函數(shù)體中的代碼當(dāng)作字符串存儲(chǔ)在內(nèi)存中(對(duì)象存儲(chǔ)的是鍵值對(duì))
- 在當(dāng)前上下文中聲明函數(shù)(變量)恋脚,函數(shù)聲明會(huì)提升到最前面
- 把開辟的堆內(nèi)存地址賦值給函數(shù)名(變量名)
此時(shí)輸出函數(shù)名 fn
(不是 fn()
),代表當(dāng)前函數(shù)本身焰手,如果我們要執(zhí)行函數(shù)糟描,就要加上小括號(hào)即 fn()
。這是兩種不同本質(zhì)的操作书妻。
【函數(shù)執(zhí)行】
目的:把之前存儲(chǔ)到堆內(nèi)存中的代碼字符串變?yōu)檎嬲?JS 代碼自上而下執(zhí)行船响,從而實(shí)現(xiàn)應(yīng)有的功能。
- 函數(shù)執(zhí)行躲履,首先會(huì)形成一個(gè)私有的作用域(一個(gè)供代碼執(zhí)行的環(huán)境见间,也是一個(gè)棧內(nèi)存)
- 把之前在堆內(nèi)存中存儲(chǔ)的字符串復(fù)制一份到新開辟的棧內(nèi)存中變?yōu)檎嬲?JS 代碼
- 然后再進(jìn)行變量提升 (var function 提前聲明, 先形參賦值工猜, 再變量提升)
- 最后在這個(gè)新開辟的作用域中自上而下執(zhí)行
函數(shù)執(zhí)行的時(shí)候缤剧,都會(huì)形成一個(gè)全新的私有作用域(私有棧內(nèi)存),目的是:
- 把原有堆內(nèi)存中存儲(chǔ)的字符串變成真正的 JS 代碼執(zhí)行
- 保護(hù)里面的私有變量不受外界的干擾(和外界隔離)
我們把函數(shù)執(zhí)行的這種保護(hù)機(jī)制域慷,稱之為“閉包”
函數(shù)內(nèi)聲明的變量都是私有變量荒辕。
3. 函數(shù)中的參數(shù)
參數(shù)是函數(shù)的入口:當(dāng)我們在函數(shù)中封裝一個(gè)功能汗销,有一些不確定的因素,需要執(zhí)行函數(shù)的時(shí)候由用戶傳遞進(jìn)來抵窒。此時(shí)我們就基于參數(shù)的機(jī)制弛针,提供入口即可。
函數(shù)中的參數(shù)是按值傳遞的
//=> 此時(shí)的參數(shù)叫做形參(命名參數(shù)):入口李皇,形參是變量
function sum(n, m) {
return n + m;
}
//=> 函數(shù)執(zhí)行時(shí)傳遞的值叫做實(shí)參:實(shí)參是具體的數(shù)據(jù)值削茁,即使寫的是變量或者表達(dá)式,也是把變量或者表達(dá)式計(jì)算的結(jié)果作為值傳遞給形參變量
sum(1, 2); //=> n:1, m:2
sum(1); //=> n:1, m:undefined
sum(); //=> n:undefined, m:undefined
sum(1, 2, 3) //=> n:1, m:2, 3 沒有形參變量接收
3.1 理解參數(shù)
JavaScript 中函數(shù)不介意傳遞多少個(gè)參數(shù)掉房,也不在乎傳進(jìn)去的參數(shù)是什么數(shù)據(jù)類型茧跋。即使定義時(shí),函數(shù)只有兩個(gè)形參卓囚,實(shí)際執(zhí)行的時(shí)候瘾杭,也可以傳遞任意個(gè)參數(shù)。
原因是 JavaScript 中的參數(shù)在內(nèi)部是用一個(gè)數(shù)組來表示的哪亿,函數(shù)接受到的始終是一個(gè)數(shù)組粥烁。
實(shí)際上,在函數(shù)內(nèi)部可以通過 arguments
對(duì)象來訪問這個(gè)參數(shù)數(shù)組蝇棉,從而獲取傳遞給函數(shù)的每一個(gè)參數(shù)讨阻。
3.2 arguments 對(duì)象
arguments
對(duì)象是一個(gè)類數(shù)組對(duì)象,可以通過索引訪問元素篡殷,也有 length
屬性訪問長度钝吮。是函數(shù)內(nèi)置的實(shí)參集合(內(nèi)置:函數(shù)天生就存在的機(jī)制,不管你是否設(shè)置形參板辽,是否傳遞實(shí)參搀绣,arguments
始終存在),只能在函數(shù)內(nèi)訪問戳气。
命名參數(shù)是有局限性的:我們需要具體知道用戶執(zhí)行的時(shí)候傳遞實(shí)參數(shù)量链患、順序等,才可以使用形參變量定義對(duì)應(yīng)入口瓶您。
通過 arguments
對(duì)象麻捻,函數(shù)不顯式的使用命名參數(shù),也能夠?qū)崿F(xiàn)一樣的功能呀袱。
function sum() {
return arguments[0] + arguments[1];
}
因此在 JavaScript 中函數(shù)的一個(gè)重要特點(diǎn)是:命名參數(shù)只提供便利贸毕,但不是必須的。
length 屬性
可以通過其 length
屬性獲取傳入?yún)?shù)的個(gè)數(shù)夜赵,利用這一點(diǎn)讓函數(shù)能夠接受任意個(gè)參數(shù)并分別實(shí)現(xiàn)適當(dāng)?shù)墓δ堋?/p>
function add() {
if (arguments.length == 1) {
return arguments[0] + 10;
} else if (arguments.length == 2) {
return arguments[0] + arguments[1];
}
...
}
arguments
同樣可以和命名參數(shù)一起使用明棍。
arguments
的映射機(jī)制
arguments
中的值會(huì)與對(duì)應(yīng)命名參數(shù)(形參)的值保持同步
function add(n, m) {
arguments[1] = 10;
return n + m;
}
add(1,2) //=> 11
// 反過來改變命名參數(shù)的值也是一樣,同步改變
arguments
對(duì)象和形參之間的映射是在函數(shù)執(zhí)行后形參賦值的一瞬間寇僧,瀏覽器通過 arguments
中的索引來完成和對(duì)應(yīng)形參變量中的映射機(jī)制搭建摊腋。一開始沒有建立起來的沸版,即使在后面改變 arguments
對(duì)象,也無法形成映射兴蒸。
如果形參比 arguments
中個(gè)數(shù)多视粮,那么多出來的形參是無法和 arguments
中對(duì)應(yīng)的索引建立關(guān)聯(lián)的。
function fn(x, y) {
/* 形參賦值:x = 10, y = undefined
*
* argumenrs
* 0: 10
* length: 1
*/
var arg = arguments;
arg[0] = 100;
console.log(x); //=> 100橙凳,存在索引蕾殴,形成映射
y = 200;
console.log(arg[1]); //=> undefined
//=> 后面再改變 arguments,也無法形成映射
arg[1] = 150;
console.log(y); //=> 200
}
fn(10);
注意:
- 這并不說明兩個(gè)值訪問相同的內(nèi)存空間岛啸,它們的內(nèi)存空間是相互獨(dú)立的钓觉,只是值會(huì)保持同步
- arguments 對(duì)象的長度由傳入的參數(shù)個(gè)數(shù)決定,不是由定義函數(shù)時(shí)的命名參數(shù)個(gè)數(shù)決定
- 沒有傳遞值的命名參數(shù)將被自動(dòng)賦值為 undefined
- 嚴(yán)格模式下坚踩,不允許改變
arguments
對(duì)象荡灾,同時(shí)與命名參數(shù)不同步
callee 屬性
存儲(chǔ)的是當(dāng)前函數(shù)本身。嚴(yán)格模式下堕虹,訪問會(huì)報(bào)錯(cuò)。
callee.caller 屬性
存儲(chǔ)的是調(diào)用函數(shù)的環(huán)境芬首。全局調(diào)用的話赴捞,值為 null
。嚴(yán)格模式下郁稍,訪問會(huì)報(bào)錯(cuò)赦政。
應(yīng)用
任意數(shù)求和:不管函數(shù)執(zhí)行的時(shí)候,傳遞多少實(shí)參值進(jìn)來耀怜,我們都可以求和
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
優(yōu)化:在累加時(shí)恢着,把字符串轉(zhuǎn)換為數(shù)字,對(duì)于非有效數(shù)字财破,不再相加掰派。
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
var item = Number(arguments[i]);
isNaN(item) ? null : total += item;
}
return total;
}
//=> ES6
var sum = (...arg) => eval(arg.filter(item => !isNaN(item)).join('+'));
4. 返回值
返回值是函數(shù)的出口,把函數(shù)運(yùn)行的結(jié)果或者函數(shù)體中的部分信息拿到函數(shù)外面去使用左痢。
函數(shù)在任何時(shí)候都可以通過 return
返回一個(gè)值靡羡,返回之后,函數(shù)停止并立即退出俊性,后面代碼不再執(zhí)行略步。
function fn(n, m) {
var total = 0;
total = n + m;
return total;
//=> 并不是把 total 變量返回,返回的是變量存儲(chǔ)的值定页,return 返回的永遠(yuǎn)是一個(gè)值
}
fn(1,2) //=> 3
要么讓函數(shù)始終返回一個(gè)值趟薄,要么始終不會(huì)返回值。
5. 匿名函數(shù)
匿名函數(shù):沒有函數(shù)名的函數(shù)
- 函數(shù)表達(dá)式:把函數(shù)當(dāng)作值賦值給變量或者元素的事件
- 自執(zhí)行函數(shù):創(chuàng)建和執(zhí)行一起完成(立即執(zhí)行匿名函數(shù))
- 回調(diào)函數(shù):將匿名函數(shù)當(dāng)作參數(shù)傳入函數(shù)中
//=> 函數(shù)表達(dá)式
var sum = function() {
};
//=> 自執(zhí)行函數(shù)
(function(){
})();
~function() {
}();
+function() {
}();
!function() {
}();