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));

擴展閱讀

編輯

<=目錄