JavaScript/函式
函式是一段程式碼塊,用於解決特定的問題並將其解決方案返回給呼叫語句。函式存在於自己的上下文中。因此,函式將龐大的程式劃分為較小的“磚塊”,從而構建軟體以及軟體開發過程。
// define a function
function <function_name> (<parameters>) {
<function_body>
}
// call a function
<variable> = <function_name> (<arguments>);
JavaScript 支援軟體開發正規化 函數語言程式設計。函式是源自 Object 的資料型別;它們可以繫結到變數,作為引數傳遞,並從其他函式返回,就像任何其他資料型別一樣。
函式可以透過三種主要方式構建。第一個版本可以進一步縮寫;見下文。
傳統方式
"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);
對於函式的宣告,我們已經看到了三種變體。對於它們的呼叫,也有三種變體。宣告和呼叫彼此獨立,您可以任意組合它們。
傳統的呼叫變體使用函式名後跟圓括號 ( )。在圓括號內,是函式的引數,如果有的話。
"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"));
必須單獨檢查每個可能未給出的引數。
如果可選引數的處理結構相同,則可以使用rest 運算子語法簡化程式碼 - 主要與迴圈結合使用。該特性的語法在函式簽名中由三個點組成 - 類似於擴充套件語法。
它是如何工作的?作為函式呼叫的一部分,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 屬性。
它的功能與上面的rest 語法相當。主要區別在於 arguments 包含所有引數,而rest 語法不一定會影響所有引數。
"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 上次呼叫,……。