從整體概述到細節,學習如何使用函數
介紹
JavaScript 中的所有內容都是在函數中執行的。
函數是一些自包含的代碼塊,可以定義一次但可以隨時運行。
函數可以選擇性地接受參數,並返回一個值。
JavaScript 中的函數屬於對象,是一種特殊類型的對象:函數對象。它們的超能力在於可以被調用。
此外,函數被稱為一等公民的函數,因為它們可以被賦值給一個值,可以作為引數傳遞和作為返回值使用。
語法
讓我們從之前的“舊”的,ES6/ES2015之前的語法開始。這是一個函數聲明的範例:
function dosomething(foo) {
// do something
}
(在 ES6/ES2015 後的語法中,稱之為常規函數)
函數也可以賦值給變量(稱為函數表達式):
const dosomething = function(foo) {
// do something
};
有名函數表達式與此類似,但與堆棧調用跟踪更為配合,當發生錯誤時非常有用-它保持了函數的名稱:
const dosomething = function dosomething(foo) {
// do something
};
ES6/ES2015 引入了箭頭函數,在處理內聯函數(作為參數或回調使用)時特別方便:
const dosomething = foo => {
// do something
};
箭頭函數與上面的其他函數定義有一個重要的區別,稍後我們會看到具體內容,因為這是一個進階話題。
參數
函數可以有一個或多個參數。
const dosomething = () => {
// do something
};
const dosomethingElse = foo => {
// do something
};
const dosomethingElseAgain = (foo, bar) => {
// do something
};
從 ES6/ES2015 開始,函數的參數可以有默認值:
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};
這允許你調用一個函數而不填寫所有參數:
dosomething(3);
dosomething();
ES2018 引入了對於參數的尾部逗號,這個特性有助於減少由於移動參數而導致的錯誤(例如,將最後一個參數移動到中間):
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};
dosomething(2, 'ho!');
你可以將所有的參數包裝在一個數組中,在調用函數時使用散列運算符(spread operator):
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};
const args = [2, 'ho!'];
dosomething(...args);
對於許多參數,記住順序可能很困難。使用對象時,解構允許保持參數名稱:
const dosomething = ({ foo = 1, bar = 'hey' }) => {
// do something
console.log(foo); // 2
console.log(bar); // 'ho!'
};
const args = { foo: 2, bar: 'ho!' };
dosomething(args);
返回值
每個函數都返回一個值,默認情況下為 undefined
。
當 JavaScript 遇到 return
關鍵字時,它會退出函數的執行,並將控制權返回給調用者。
如果你傳遞一個值,該值將作為函數的結果返回:
const dosomething = () => {
return 'test';
};
const result = dosomething(); // result === 'test'
你只能返回一個值。
為了"模擬"返回多個值,可以返回一個對象字面量或一個數組,在呼叫函數時使用解構賦值。
使用數組:
使用對象:
嵌套函數
函數可以在其他函數內部定義:
const dosomething = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};
嵌套函數的作用域限制在外部函數內部,無法在外部調用。
這意味著 dosomethingelse()
無法從外部的 dosomething()
中調用:
const dosomething = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};
dosomethingelse(); // ReferenceError: dosomethingelse is not defined
這非常有用,因為我們可以創建封裝的代碼,它的作用域受到它所定義的外部函數的限制。
我們可以有兩個函數在它們內部定義同名的函數:
const bark = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};
const sleep = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};
最重要的是,您無需擔心覆蓋其他函數和變量的定義。
物件方法
當函數用作對象的屬性時,它們被稱為方法:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started`);
}
};
car.start();
箭頭函數中的 this
在箭頭函數與常規函數用作對象方法時,它們有一個重要的行為差異。考慮以下範例:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started ${this.brand} ${this.model}`);
},
stop: () => {
console.log(`Stopped ${this.brand} ${this.model}`);
}
};
stop()
方法並不能按您預期的方式工作。
這是因為在兩種函數聲明形式中,this
的處理方式是不同的。箭頭函數中的 this
指的是外層函數的上下文,因此在這種情況下指的是 window
對象。
而常規函數中則使用 function()
來指向宿主對象。
這意味著箭頭函數不適合用於物件方法和構造函數(使用箭頭函數的構造函數實際上會引發 TypeError
)。
立即呼叫函數表達式 (IIFE)
IIFE是一種在聲明後立即執行的函數:
;(function dosomething() {
console.log('executed');
})();
你可以將結果賦值給一個變量:
const something = (function dosomething() {
return 'something';
})();
它們非常方便,因為你不需要在定義之後再單獨調用該函數。
函數提升
JavaScript 在執行代碼之前會根據一些規則重新排序代碼。
特別是函數會移動到其作用域的頂部。這就是為什麼可以寫出類似這樣的代碼:
dosomething();
function dosomething() {
console.log('did something');
}
在內部,JavaScript 將該函數與所在作用域中找到的所有其他函數一起移動。
function dosomething() {
console.log('did something');
}
dosomething();
現在,如果使用有名函數表達式,由於使用了變量,情況就有所不同。變量聲明被提升,但值不會被提升,所以函數也不會被提升。
dosomething();
const dosomething = function dosomething() {
console.log('did something');
};
這樣是行不通的:
這是因為在內部實際上發生的是:
const dosomething;
dosomething();
dosomething = function dosomething() {
console.log('did something');
};
let
聲明也是如此。var
聲明也不起作用,但錯誤類型不同:
這是因為 var
聲明被提升並初始化為 undefined
,而 const
和 let
被提升但不初始化。