JavaScript/子程序和對象
函數(function)是 JavaScript 最重要部分之一。你可以用它整理代碼、減少代碼長度或建立參數。
無參數的函數
編輯無參數的函數是十分簡單的。
<script language="javascript">
function helloworld()
{
alert("Hello, World!");
}
</script>
當我們要在 HTML 文檔內調用這個函數,則用:
helloworld();
現在組裝入網頁:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML>
<HEAD>
<TITLE>Some Page</TITLE>
<script language="javascript">
function helloworld()
{
alert("Hello World!");
}
</script>
</HEAD>
<BODY>
<script lang="javascript">
helloworld();
</script>
</BODY>
</HTML>
不難吧?在這個問題上,我們只採用函數去節省不必要的代碼輸入(而事實上上述示例確實增加了代碼輸入,但只要把握這個思想,大多數情況下都是合用的),但當你學到帶有參數的函數時,那才是函數發光的原因!
帶參數的函數
編輯我們就以一個簡單的例子開始,然後才深入瞭解。 包含有過程和結果返回。
function addsubtract(thenumber)
{
if(thenumber>5){
thenumber--;
}
if(thenumber<5){
thenumber++;
}
return thenumber;
}
這個程序以數字為變數。如果thenumber大於5,那麼thenumber就減一;如果thenumber小於5,thenumber則加一。
分解程序:
function addsubtract(thenumber){......}
你應該見過這個東西,只不過是有點不一樣而已。現在在函數名稱後面多了一個(thenumber)
。那裡就是我們定義一個將要使用的參數的地方(記得定義變量嗎?它們有相似的一面,但定義參數我們不用var都可以)。
if(thenumber>5){......}
這是假定(if)語句。如果圓括號內的語句是真(true),那麼就繼續執行波形括號內的語句。相似的方法有:
X>Y(如果X大於Y) X<Y(如果X小於Y) X>=Y(如果X大於或等於Y) X<=Y(如果X小於或等於Y) X==Y(如果X等於Y)
將有更多類似方法,將在日後說明。
thenumber--;
假如Javascript是你的第一編程語言,你將不知道這是什麼意思。這十分簡單,就是對變量thenumber
減一。這是thenumber = thenumber - 1;
的縮寫版本。
thenumber++;
與上述一樣,只不過不是減而是加。
return thenumber;
這表示返回thenumber的值,後述將詳細提及。
下面的代碼就自己研究吧:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML>
<HEAD>
<TITLE>Some Page</TITLE>
<script language="javascript">
function addsubtract(thenumber)
{
if(thenumber>5){
thenumber--;
}
if(thenumber<5){
thenumber++;
}
return thenumber;
}
</script>
</HEAD>
<BODY>
<script language="javascript">
var number = addsubtract(6);
alert(number);
</script>
</BODY>
</HTML>
還有...
var number = addsubtract(6);
這就是方便的地方了。number的值將為5,因為程序addsubtract的參數為6。
函數概論
編輯函數存在於它自己的上下文(context)中。
// define a function
function <function_name> (<parameters>) {
<function_body>
}
// call a function
<variable> = <function_name> (<arguments>);
JavaScript支持函數式編程。函數派生自Object的一種數據類型。是頭等公民。
函數聲明
編輯函數有3種聲明方式。傳統方式為:
"use strict";
// conventional declaration (or 'definition')
function duplication(p) {
return p + "! " + p + "!";
}
// call the function
const ret = duplication("Go");
alert(ret);
Construction via a variable and an expression:
"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種函數調用的辦法。傳統辦法是函數名後面跟隨一對圓括號( )
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// the conventional invocation method
const ret = duplication("Go");
alert(ret);
如果腳本運行在瀏覽器中,有2種調用方式。使用瀏覽器提供的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 + "!";
}
立即被調函數
編輯可以把函數聲明與函數調用寫在一起,稱「立即被調函數表達式」(Immediately Invoked Function Expression, IIEF):
"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")
);
實參
編輯函數調用時,函數的形參會被實參替換。
傳值調用
編輯函數的實參值複製給形參。形參值改編不影響實參。
"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));
為什麼會這樣?它與基礎數據類型有不同的行為嗎?並不!
函數接收了對象引用的拷貝。 因此,在函數內引用了同一個對象。對象本身只存在一次,但有兩個(相同的)對該對象的引用。對象的屬性是否由一個引用或另一個引用修改並沒有什麼區別。
另一個後果是引用本身的修改(例如,通過創建新對象)在外部例程中將不可見。直觀上這與基礎數據類型有相同行為。對新對象的引用存儲在原引用的「拷貝」中。 現在我們不僅有兩個引用(具有不同的值)而且還有兩個不同的對象。
"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:這種參數傳遞技術的命名在不同語言中的使用並不一致。 有時它被稱為「共享調用」(call-by-sharing)。
注 2:上述描述的 JavaScript 參數傳遞的後果與使用關鍵字 const
的後果相當。後者將變量聲明為「constant」。 此類變量無法更改。然而,如果它們引用了一個對象,則該對象的屬性可以更改。
預設值
編輯如果調用函數時使用的參數少於其聲明的形式參數,則多餘的形式參數將保持未定義狀態。 但是您可以通過在函數簽名內賦一個值來定義這種情況的預設值。 缺少對應實參的形式參數將得到它們的預設值。
"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
可變數量的實參
編輯對於某些函數,它們涉及不同數量的參數是「正常」的。 例如,考慮一個顯示名稱的函數。 在任何情況下都必須給出名字和姓氏,但也可能必須顯示學術頭銜或貴族頭銜。 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 operator)語法來簡化代碼 - 主要與循環結合使用。 該功能的語法由函數簽名中的三個點組成 - 就像擴展語法(spread syntax)一樣。
它是如何工作的?作為函數調用的一部分,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"));
調用的第三個和所有後續參數被收集到一個數組中,該數組可在函數中作為最後一個參數使用。 這允許使用循環並簡化函數的原始碼。
'arguments'關鍵字
編輯與C系列其他程式語言一樣,JavaScript在函數中提供了關鍵字arguments
。 它是一個類似數組的對象,包含函數調用的所有給定參數。 您可以循環訪問它或使用它的length
屬性。
它的功能與上面的「剩餘語法」(rest syntax)相當。 主要區別在於 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
編輯如果沒有返回值,則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);
}
箭頭函數(=>)
編輯箭頭函數(arrow function)是上述傳統函數語法的緊湊可選形式。
"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
。
"use strict";
const myArray = ['a', 'b', 'c'];
myArray.forEach(e => alert("The element of the array is: " + e));
其他程式語言在匿名函數或lambda表達式等術語下提供箭頭函數的概念。
遞歸調用
編輯"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));
擴展閱讀
編輯- 十分鐘搞定JavaScript(Javascript in Ten Minutes):快速講解對像形的JavaScript。 (目前已經失連)
- MDN: Functions
- MDN: Rest parameter