/

JavaScript 函數

JavaScript 函數

從整體概述到細節,學習如何使用函數

介紹

JavaScript 中的所有內容都是在函數中執行的。

函數是一些自包含的代碼塊,可以定義一次但可以隨時運行。

函數可以選擇性地接受參數,並返回一個值。

JavaScript 中的函數屬於對象,是一種特殊類型的對象:函數對象。它們的超能力在於可以被調用。

此外,函數被稱為一等公民的函數,因為它們可以被賦值給一個值,可以作為引數傳遞和作為返回值使用。

語法

讓我們從之前的“舊”的,ES6/ES2015之前的語法開始。這是一個函數聲明的範例

1
2
3
function dosomething(foo) {
// do something
}

(在 ES6/ES2015 後的語法中,稱之為常規函數)

函數也可以賦值給變量(稱為函數表達式):

1
2
3
const dosomething = function(foo) {
// do something
};

有名函數表達式與此類似,但與堆棧調用跟踪更為配合,當發生錯誤時非常有用-它保持了函數的名稱:

1
2
3
const dosomething = function dosomething(foo) {
// do something
};

ES6/ES2015 引入了箭頭函數,在處理內聯函數(作為參數或回調使用)時特別方便:

1
2
3
const dosomething = foo => {
// do something
};

箭頭函數與上面的其他函數定義有一個重要的區別,稍後我們會看到具體內容,因為這是一個進階話題。

參數

函數可以有一個或多個參數。

1
2
3
4
5
6
7
8
9
10
11
const dosomething = () => {
// do something
};

const dosomethingElse = foo => {
// do something
};

const dosomethingElseAgain = (foo, bar) => {
// do something
};

從 ES6/ES2015 開始,函數的參數可以有默認值:

1
2
3
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};

這允許你調用一個函數而不填寫所有參數:

1
2
dosomething(3);
dosomething();

ES2018 引入了對於參數的尾部逗號,這個特性有助於減少由於移動參數而導致的錯誤(例如,將最後一個參數移動到中間):

1
2
3
4
5
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};

dosomething(2, 'ho!');

你可以將所有的參數包裝在一個數組中,在調用函數時使用散列運算符(spread operator):

1
2
3
4
5
6
7
const dosomething = (foo = 1, bar = 'hey') => {
// do something
};

const args = [2, 'ho!'];

dosomething(...args);

對於許多參數,記住順序可能很困難。使用對象時,解構允許保持參數名稱:

1
2
3
4
5
6
7
8
9
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

返回 undefined

當 JavaScript 遇到 return 關鍵字時,它會退出函數的執行,並將控制權返回給調用者。

如果你傳遞一個值,該值將作為函數的結果返回:

1
2
3
4
5
const dosomething = () => {
return 'test';
};

const result = dosomething(); // result === 'test'

你只能返回一個值。

為了”模擬”返回多個值,可以返回一個對象字面量或一個數組,在呼叫函數時使用解構賦值。

使用數組:

使用數組解構賦值

使用對象:

使用對象解構賦值

嵌套函數

函數可以在其他函數內部定義:

1
2
3
4
5
6
7
const dosomething = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};

嵌套函數的作用域限制在外部函數內部,無法在外部調用。

這意味著 dosomethingelse() 無法從外部的 dosomething() 中調用:

1
2
3
4
5
6
7
8
9
const dosomething = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};

dosomethingelse(); // ReferenceError: dosomethingelse is not defined

這非常有用,因為我們可以創建封裝的代碼,它的作用域受到它所定義的外部函數的限制。

我們可以有兩個函數在它們內部定義同名的函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const bark = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};

const sleep = () => {
const dosomethingelse = () => {
// 在這裡寫代碼
};
dosomethingelse();
return 'test';
};

最重要的是,您無需擔心覆蓋其他函數和變量的定義。

物件方法

當函數用作對象的屬性時,它們被稱為方法:

1
2
3
4
5
6
7
8
9
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started`);
}
};

car.start();

箭頭函數中的 this

在箭頭函數與常規函數用作對象方法時,它們有一個重要的行為差異。考慮以下範例:

1
2
3
4
5
6
7
8
9
10
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 的處理方式是不同的。箭頭函數中的 this 指的是外層函數的上下文,因此在這種情況下指的是 window 對象。

`this` 指向 window 對象

而常規函數中則使用 function() 來指向宿主對象。

這意味著箭頭函數不適合用於物件方法和構造函數(使用箭頭函數的構造函數實際上會引發 TypeError)。

立即呼叫函數表達式 (IIFE)

IIFE是一種在聲明後立即執行的函數:

1
2
3
;(function dosomething() {
console.log('executed');
})();

你可以將結果賦值給一個變量:

1
2
3
const something = (function dosomething() {
return 'something';
})();

它們非常方便,因為你不需要在定義之後再單獨調用該函數。

函數提升

JavaScript 在執行代碼之前會根據一些規則重新排序代碼。

特別是函數會移動到其作用域的頂部。這就是為什麼可以寫出類似這樣的代碼:

1
2
3
4
dosomething();
function dosomething() {
console.log('did something');
}

提升範例

在內部,JavaScript 將該函數與所在作用域中找到的所有其他函數一起移動。

1
2
3
4
function dosomething() {
console.log('did something');
}
dosomething();

現在,如果使用有名函數表達式,由於使用了變量,情況就有所不同。變量聲明被提升,但值不會被提升,所以函數也不會被提升。

1
2
3
4
dosomething();
const dosomething = function dosomething() {
console.log('did something');
};

這樣是行不通的:

提升有名函數

這是因為在內部實際上發生的是:

1
2
3
4
5
const dosomething;
dosomething();
dosomething = function dosomething() {
console.log('did something');
};

let 聲明也是如此。var 聲明也不起作用,但錯誤類型不同:

提升 var 聲明

這是因為 var 聲明被提升並初始化為 undefined,而 constlet 被提升但不初始化。