函式
Baseline 廣泛可用 *
通常來說,函式是一種“子程式”,可以由函式外部(或在遞迴的情況下由函式內部)的程式碼呼叫。與程式本身一樣,函式由一系列被稱為函式體的語句組成。值可以作為引數傳遞給函式,函式將返回一個值。
在 JavaScript 中,函式是一等物件,因為它們可以作為引數傳遞給其他函式,從函式中返回,並分配給變數和屬性。它們也可以像其他任何物件一樣擁有屬性和方法。與其他物件的區別在於函式可以被呼叫。
有關更多示例和解釋,請參閱JavaScript 函式指南。
描述
函式值通常是 Function 的例項。有關 Function 物件的屬性和方法的資訊,請參閱 Function。可呼叫值會導致 typeof 返回 "function" 而不是 "object"。
注意:並非所有可呼叫值都是 instanceof Function。例如,Function.prototype 物件是可呼叫的,但不是 Function 的例項。你還可以手動設定函式的原型鏈,使其不再繼承自 Function.prototype。然而,這種情況極其罕見。
返回值
預設情況下,如果函式的執行沒有以 return 語句結束,或者 return 關鍵字後沒有表示式,則返回值為 undefined。return 語句允許你從函式中返回任意值。一次函式呼叫只能返回一個值,但你可以透過返回一個物件或陣列並解構結果來模擬返回多個值的效果。
注意:使用 new 呼叫的建構函式有一套不同的邏輯來確定它們的返回值。
傳遞引數
引數 (Parameters) 和實參 (arguments) 的含義略有不同,但在 MDN web docs 中,我們經常互換使用它們。快速參考:
function formatNumber(num) {
return num.toFixed(2);
}
formatNumber(2);
在這個例子中,num 變數被稱為函式的形參:它在函式定義中的圓括號列表中宣告。函式期望 num 形參是一個數字——儘管在 JavaScript 中,如果不編寫執行時驗證程式碼,這是無法強制執行的。在 formatNumber(2) 呼叫中,數字 2 是函式的實參:它是函式呼叫中實際傳遞給函式的值。實參值可以透過相應的形參名稱或 arguments 物件在函式體內部訪問。
實參總是按值傳遞,從不按引用傳遞。這意味著如果函式重新分配一個形參,其值在函式外部不會改變。更準確地說,物件實參是按共享傳遞,這意味著如果物件的屬性被修改,這種改變會影響函式外部。例如:
function updateBrand(obj) {
// Mutating the object is visible outside the function
obj.brand = "Toyota";
// Try to reassign the parameter, but this won't affect
// the variable's value outside the function
obj = null;
}
const car = {
brand: "Honda",
model: "Accord",
year: 1998,
};
console.log(car.brand); // Honda
// Pass object reference to the function
updateBrand(car);
// updateBrand mutates car
console.log(car.brand); // Toyota
this 關鍵字指向函式被訪問的物件——它不指向當前執行的函式,因此你必須透過名稱來引用函式值,即使在函式體內部也是如此。
定義函式
廣義地說,JavaScript 有四種函式:
- 普通函式:可以返回任何值;呼叫後總是執行到完成
- 生成器函式:返回一個
Generator物件;可以使用yield運算子暫停和恢復 - 非同步函式:返回一個
Promise;可以使用await運算子暫停和恢復 - 非同步生成器函式:返回一個
AsyncGenerator物件;可以同時使用await和yield運算子
對於每種函式,都有多種定義方式:
此外,還有定義箭頭函式和方法的特殊語法,它們為使用提供了更精確的語義。類在概念上不是函式(因為它們在沒有 new 的情況下呼叫時會丟擲錯誤),但它們也繼承自 Function.prototype 並且 typeof MyClass === "function"。
// Constructor
const multiply = new Function("x", "y", "return x * y");
// Declaration
function multiply(x, y) {
return x * y;
} // No need for semicolon here
// Expression; the function is anonymous but assigned to a variable
const multiply = function (x, y) {
return x * y;
};
// Expression; the function has its own name
const multiply = function funcName(x, y) {
return x * y;
};
// Arrow function
const multiply = (x, y) => x * y;
// Method
const obj = {
multiply(x, y) {
return x * y;
},
};
所有語法的功能大致相同,但存在一些細微的行為差異。
Function()建構函式、function表示式和function宣告語法建立的是完整的函式物件,可以使用new進行構造。然而,箭頭函式和方法不能被構造。非同步函式、生成器函式和非同步生成器函式無論使用何種語法都不可構造。function宣告建立的函式是提升的。其他語法不提升函式,函式值只在定義後可見。- 箭頭函式和
Function()建構函式總是建立匿名函式,這意味著它們不能輕易地遞迴呼叫自身。遞迴呼叫箭頭函式的一種方法是將其賦值給一個變數。 - 箭頭函式語法無法訪問
arguments或this。 Function()建構函式無法訪問任何區域性變數——它只能訪問全域性作用域。Function()建構函式會導致執行時編譯,並且通常比其他語法慢。
對於 function 表示式,函式名與函式被賦值的變數之間存在區別。函式名不能更改,而函式被賦值的變數可以被重新賦值。函式名可以與函式被賦值的變數不同——它們之間沒有關係。函式名只能在函式體內部使用。嘗試在函式體外部使用它會導致錯誤(或者如果其他地方聲明瞭同名變數,則獲取另一個值)。例如:
const y = function x() {};
console.log(x); // ReferenceError: x is not defined
另一方面,函式被賦值的變數僅受其作用域限制,該作用域保證包含函式宣告的作用域。
函式宣告還會建立一個與函式名同名的變數。因此,與透過函式表示式定義的函式不同,透過函式宣告定義的函式可以透過其名稱在其定義的作用域中以及在其自身體內訪問。
透過 new Function 定義的函式會動態地組裝其原始碼,這在將其序列化時是可見的。例如,console.log(new Function().toString()) 會輸出:
function anonymous(
) {
}
這是用於編譯函式的實際原始碼。然而,儘管 Function() 建構函式會建立名為 anonymous 的函式,但這個名稱並沒有新增到函式體的作用域中。函式體只能夠訪問全域性變數。例如,下面的程式碼會導致錯誤:
new Function("alert(anonymous);")();
透過函式表示式或函式宣告定義的函式繼承當前作用域。也就是說,函式形成一個閉包。另一方面,透過 Function 建構函式定義的函式不繼承任何除全域性作用域以外的作用域(所有函式都繼承全域性作用域)。
// p is a global variable
globalThis.p = 5;
function myFunc() {
// p is a local variable
const p = 9;
function decl() {
console.log(p);
}
const expr = function () {
console.log(p);
};
const cons = new Function("\tconsole.log(p);");
decl();
expr();
cons();
}
myFunc();
// Logs:
// 9 (for 'decl' by function declaration (current scope))
// 9 (for 'expr' by function expression (current scope))
// 5 (for 'cons' by Function constructor (global scope))
透過函式表示式和函式宣告定義的函式只解析一次,而透過 Function 建構函式定義的函式在每次呼叫建構函式時都會解析傳遞給它的字串。儘管函式表示式每次都會建立一個閉包,但函式體不會被重新解析,因此函式表示式仍然比 new Function(...) 快。因此,應儘可能避免使用 Function 建構函式。
當函式宣告出現在表示式上下文中時,可能會無意中變為函式表示式。
// A function declaration
function foo() {
console.log("FOO!");
}
doSomething(
// A function expression passed as an argument
function foo() {
console.log("FOO!");
},
);
另一方面,函式表示式也可能變成函式宣告。一個表示式語句不能以 function 或 async function 關鍵字開頭,這是實現 IIFE(立即執行函式表示式)時常見的錯誤。
function () { // SyntaxError: Function statements require a function name
console.log("FOO!");
}();
function foo() {
console.log("FOO!");
}(); // SyntaxError: Unexpected token ')'
相反,請以其他方式開始表示式語句,以便 function 關鍵字明確地開始一個函式表示式。常見的選項包括分組和使用 void。
(function () {
console.log("FOO!");
})();
void function () {
console.log("FOO!");
}();
函式引數
每個函式引數都是一個簡單的識別符號,你可以在區域性作用域中訪問它。
function myFunc(a, b, c) {
// You can access the values of a, b, and c here
}
有三種特殊的引數語法:
function myFunc({ a, b }, c = 1, ...rest) {
// You can access the values of a, b, c, and rest here
}
如果使用了上述非簡單引數語法中的一種,則會產生一些後果:
- 你不能將
"use strict"應用於函式體——這會導致語法錯誤。 - 即使函式不在嚴格模式下,某些嚴格模式下的函式特性也適用,包括
arguments物件停止與命名引數同步,訪問arguments.callee會丟擲錯誤,並且不允許重複的引數名。
arguments 物件
你可以透過使用 arguments 物件在函式內部引用函式的實參。
arguments-
一個類陣列物件,包含傳遞給當前執行函式的實參。
arguments.callee-
當前正在執行的函式。
arguments.length-
傳遞給函式的實引數量。
getter 和 setter 函式
你可以為任何支援新增新屬性的標準內建物件或使用者定義物件定義訪問器屬性。在物件字面量和類中,你可以使用特殊語法來定義訪問器屬性的 getter 和 setter。
請注意,這些語法建立的是一個物件屬性,而不是一個方法。getter 和 setter 函式本身只能透過反射 API(如 Object.getOwnPropertyDescriptor())進行訪問。
塊級函式
在嚴格模式下,塊內的函式作用域是該塊。在 ES2015 之前,嚴格模式下禁止塊級函式。
"use strict";
function f() {
return 1;
}
{
function f() {
return 2;
}
}
f() === 1; // true
// f() === 2 in non-strict mode
非嚴格程式碼中的塊級函式
一句話:不要使用。
在非嚴格程式碼中,塊內的函式宣告行為怪異。例如:
if (shouldDefineZero) {
function zero() {
// DANGER: compatibility risk
console.log("This is zero.");
}
}
這在嚴格模式下的語義是明確的——zero 僅存在於 if 塊的作用域內。如果 shouldDefineZero 為假,則 zero 永遠不應被定義,因為該塊從不執行。然而,歷史上,這並未明確指定,因此不同瀏覽器在非嚴格模式下的實現方式不同。有關更多資訊,請參閱function 宣告參考。
一種更安全地有條件地定義函式的方法是將函式表示式賦值給一個變數:
// Using a var makes it available as a global variable,
// with closer behavior to a top-level function declaration
var zero;
if (shouldDefineZero) {
zero = function () {
console.log("This is zero.");
};
}
示例
返回格式化數字
以下函式返回一個字串,其中包含一個用前導零填充的數字的格式化表示。
// This function returns a string padded with leading zeros
function padZeros(num, totalLen) {
let numStr = num.toString(); // Initialize return value as string
const numZeros = totalLen - numStr.length; // Calculate no. of zeros
for (let i = 1; i <= numZeros; i++) {
numStr = `0${numStr}`;
}
return numStr;
}
以下語句呼叫 padZeros 函式。
let result;
result = padZeros(42, 4); // returns "0042"
result = padZeros(42, 2); // returns "42"
result = padZeros(5, 4); // returns "0005"
確定函式是否存在
你可以使用 typeof 運算子來確定函式是否存在。在以下示例中,執行測試以確定 window 物件是否有一個名為 noFunc 的屬性是一個函式。如果是,則使用它;否則,採取其他操作。
if (typeof window.noFunc === "function") {
// use noFunc()
} else {
// do something else
}
請注意,在 if 測試中,使用的是對 noFunc 的引用——函式名後沒有括號 (),因此不會實際呼叫函式。
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-function-definitions |
瀏覽器相容性
載入中…