JavaScript/函式
函式是解決特定問題的程式碼塊,並將解決方案返回給呼叫語句。函式存在於它自己的上下文中。因此,函式將龐大的程式分解成更小的“磚塊”,這些磚塊構建了軟體及其開發過程。
// define a function
function <function_name> (<parameters>) {
<function_body>
}
// call a function
<variable> = <function_name> (<arguments>);
JavaScript 支援軟體開發正規化 函數語言程式設計。函式是派生自物件的 資料型別;它們可以繫結到變數,作為引數傳遞,並從其他函式返回,就像任何其他資料型別一樣。
函式可以透過三種主要方式構造。第一種版本可以進一步簡化;見下文。
傳統方式
"use strict";
// conventional declaration (or 'definition')
function duplication(p) {
return p + "! " + p + "!";
}
// call the function
const ret = duplication("Go");
alert(ret);
透過變數和表示式進行構造
"use strict";
// assign the function to a variable
let duplication = function (p) {
return p + "! " + p + "!";
};
const ret = duplication("Go");
alert(ret);
透過new運算子進行構造(此版本有點繁瑣)
"use strict";
// using the 'new' constructor
let duplication = new Function ("p",
"return p + '! ' + p + '!'");
const ret = duplication("Go");
alert(ret);
對於函式的宣告,我們已經看到了 3 種變體。對於它們的呼叫,也有 3 種變體。宣告和呼叫是相互獨立的,您可以隨意組合它們。
傳統的呼叫變體使用函式名後跟圓括號( )。圓括號內是函式的引數(如果存在)。
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// the conventional invocation method
const ret = duplication("Go");
alert(ret);
如果指令碼在瀏覽器中執行,還有兩種可能性。它們使用瀏覽器提供的window物件。
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// via 'call'
let ret = duplication.call(window, "Go");
alert(ret);
// via 'apply'
ret = duplication.apply(window, ["Go"]);
alert(ret);
提示:如果您使用函式名而不帶圓括號(),您將收到函式本身(指令碼),而不是任何呼叫結果。
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
alert(duplication); // 'function duplication (p) { ... }'
函式會受到“提升”的影響。此機制會自動將函式的宣告轉移到其作用域的頂部。因此,您可以從原始碼中比其宣告更上層的位置呼叫函式。
"use strict";
// use a function above (in source code) its declaration
const ret = duplication("Go");
alert(ret);
function duplication(p) {
return p + "! " + p + "!";
}
到目前為止,我們已經看到了宣告和呼叫這兩個獨立的步驟。還有一種語法變體允許將兩者組合起來。它的特點是在函式宣告周圍使用圓括號,後面跟著()來呼叫該宣告。
"use strict";
alert( // 'alert' to show the result
// declaration plus invocation
(function (p) {
return p + "! " + p + "!";
})("Go") // ("Go"): invocation with the argument "Go"
);
alert(
// the same with 'arrow' syntax
((p) => {
return p + "! " + p + "!";
})("Gooo")
);
此語法被稱為 立即呼叫函式表示式 (IIFE)。
當函式被呼叫時,宣告階段的引數被呼叫時的引數替換。在上面的宣告中,我們使用變數名p作為引數名。當呼叫函式時,我們大多使用字面量“Go”作為引數。在執行時,它會替換函式中所有p的出現。
此類替換是 '按值' 而不是'按引用'完成的。引數原始值的副本被傳遞給函式。如果此複製的值在函式內被改變,則函式外部的原始值不會改變。
"use strict";
// there is one parameter 'p'
function duplication(p) {
// In this example, we change the parameter's value
p = "NoGo";
alert("In function: " + p);
return p + "! " + p + "!";
};
let x = "Go";
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Variable: " + x);
對於物件(所有非基本資料型別),這種“按值呼叫”可能會產生驚人的效果。如果函式修改了物件的屬性,則此更改在外部也能看到。
"use strict";
function duplication(p) {
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? Yes.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
當示例執行時,它顯示在呼叫duplication之後,函式所做的更改不僅在返回值中可見。此外,原始物件x的屬性也已改變。為什麼會這樣?它與基本資料型別的行為不同嗎?不。
函式接收對該物件的引用的副本。因此,在函式內部,引用的是同一個物件。該物件本身只存在一次,但有兩個(相同的)對該物件的引用。使用哪個引用修改物件的屬性並沒有區別。
另一個結果是(這可能與基本資料型別直觀地相同?)對引用本身的修改(例如,透過建立新的物件)在外部例程中不可見。對新物件的引用儲存在原始引用的副本中。現在我們不僅有兩個(值不同的)引用,而且還有兩個物件。
"use strict";
function duplication(p) {
// modify the reference by creating a new object
p = {};
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
註釋 1:這種引數傳遞技術的名字在不同語言中並不一致。有時它被稱為“按共享呼叫”。維基百科 對此進行了概述。
註釋 2:JavaScript 引數傳遞帶來的描述後果與使用關鍵字const帶來的後果類似,const宣告一個變數為常量。此類變數無法改變。然而,如果它們引用了一個物件,那麼該物件的屬性可以被改變。
如果函式被呼叫的引數少於它包含的引數,那麼多餘的引數將保持未定義。但是您可以透過在函式簽名中分配一個值來為這種情況定義預設值。缺少的引數將獲得這些值作為其預設值。
"use strict";
// two nearly identical functions; only the signature is slightly different
function f1(a, b) {
alert("The second parameter is: " + b)
};
function f2(a, b = 10) {
alert("The second parameter is: " + b)
};
// identical invocations; different results
f1(5); // undefined
f1(5, 100); // 100
f2(5); // 10
f2(5, 100); // 100
對於一些函式來說,它們接收不同數量的引數是“正常的”。例如,考慮一個顯示姓名的函式。firstName和familyName必須始終提供,但也有可能需要顯示academicTitle或titleOfNobility。JavaScript 提供了不同的方法來處理這種情況。
可以檢查“正常”引數和附加引數,以確定它們是否包含值。
"use strict";
function showName(firstName, familyName, academicTitle, titleOfNobility) {
"use strict";
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
if (academicTitle) {
ret = ret + ", " + academicTitle;
}
if (titleOfNobility) {
ret = ret + ", " + titleOfNobility;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D."));
alert(showName("Tom"));
每個可能沒有提供的引數都必須單獨檢查。
如果可選引數的處理在結構上是相同的,則可以使用剩餘運算子語法來簡化程式碼——通常與迴圈結合使用。該功能的語法在函式簽名中由三個點組成——就像在展開語法中一樣。
它是如何運作的?作為函式呼叫的一部分,JavaScript 引擎會將給定的可選引數合併到一個數組中。(請注意,呼叫指令碼並不會使用陣列。)這個陣列作為最後一個引數傳遞給函式。
"use strict";
// the three dots (...) introduces the 'rest syntax'
function showName(firstName, familyName, ...titles) {
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
for (const title of titles) {
ret = ret + ", " + title;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D.", "Duke"));
alert(showName("Tom"));
呼叫中的第三個引數以及所有後續引數將被收集到一個數組中,該陣列可在函式中作為最後一個引數使用。這允許使用迴圈並簡化函式的原始碼。
與 C 語言家族的其他成員一致,JavaScript 在函式中提供了 arguments 關鍵字。它是一個類似陣列的物件,包含函式呼叫中給定的所有引數。您可以迴圈遍歷它或使用它的 length 屬性。
它的功能與上述的 *剩餘語法* 相當。主要區別在於 arguments 包含 **所有** 引數,而 *剩餘語法* 不一定影響所有引數。
"use strict";
function showName(firstName, familyName, academicTitles, titlesOfNobility) {
// handle ALL parameters with a single keyword
for (const arg of arguments) {
alert(arg);
}
}
showName("Mike", "Spencer", "Ph.D.", "Duke");
函式的目的是提供針對特定問題的解決方案。這個解決方案透過 return 語句返回給呼叫程式。
它的語法是 return <表示式>,其中 <表示式> 是可選的。
函式執行直到它遇到這樣的 return 語句(或發生未捕獲的異常,或在最後一個語句之後)。<表示式> 可以是一個簡單變數,可以是任何資料型別,例如 return 5,也可以是一個複雜表示式,例如 return myString.length,也可以完全省略:return。
如果 return 語句中沒有 <表示式>,或者根本沒有遇到 return 語句,則返回 undefined。
"use strict";
function duplication(p) {
if (typeof p === 'object') {
return; // return value: 'undefined'
}
else if (typeof p === 'string') {
return p + "! " + p + "!";
}
// implicit return with 'undefined'
}
let arg = ["Go", 4, {a: 1}];
for (let i = 0; i < arg.length; i++) {
const ret = duplication(arg[i]);
alert(ret);
}
箭頭函式是上面展示的傳統函式語法的緊湊替代方案。它們縮寫了一些語言元素,省略了其他元素,並且與原始語法相比,只有幾個 語義區別。
它們始終是匿名函式,但可以被賦值給變數。
"use strict";
// original conventional syntax
function duplication(p) {
return p + "! " + p + "!";
}
// 1. remove keyword 'function' and function name
// 2. introduce '=>' instead
// 3. remove 'return'; the last value is automatically returned
(p) => {
p + "! " + p + "!"
}
// remove { }, if only one statement
(p) => p + "! " + p + "!"
// Remove parameter parentheses if it is only one parameter
// -----------------------------
p => p + "! " + p + "!" // that's all!
// -----------------------------
alert(
(p => p + "! " + p + "!")("Go")
);
以下是一個使用陣列的例子。forEach 方法迴圈遍歷陣列,並依次生成一個數組元素。這被傳遞給箭頭函式的單個引數 e。箭頭函式會顯示 e 和簡短文字。
"use strict";
const myArray = ['a', 'b', 'c'];
myArray.forEach(e => alert("The element of the array is: " + e));
其他程式語言在匿名函式或 lambda 表示式等術語下提供了箭頭函式的概念。
函式可以呼叫其他函式。在實際應用中,這種情況很常見。
當函式呼叫自身時,就會出現特殊情況。這被稱為 *遞迴呼叫*。當然,這意味著存在無限迴圈的風險。您必須更改引數以避免這種情況。
通常,當應用程式處理樹狀結構時,例如 物料清單、DOM 樹 或 家譜資訊 時,就會出現對這種遞迴呼叫的需求。這裡,我們介紹了易於實現的數學問題 階乘 計算。
階乘是小於或等於某個數字 的所有正整數的乘積,寫成 。例如, 。它可以透過從 到 的迴圈來解決,但它也存在遞迴解決方案。 的階乘是已經計算過的 的階乘乘以 ,或者用公式表示為: 。這種想法導致了相應的函式遞迴構造
"use strict";
function factorial(n) {
if (n > 0) {
const ret = n * factorial(n-1);
return ret;
} else {
// n = 0; 0! is 1
return 1;
}
}
const n = 4;
alert(factorial(n));
只要 大於 ,指令碼會再次呼叫 factorial 函式,但這次傳入的引數是 。因此,引數會逐漸收斂到 。當達到 時,這是 factorial 函式第一次不再被呼叫。它返回 的值。這個數字乘以之前 factorial 函式呼叫時傳遞的下一個較大的數字。乘法的結果將返回給之前呼叫 factorial 函式的函式......