跳至內容

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

對於函式的宣告,我們已經看到了 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 的結果相當,該關鍵字將變數宣告為常量。此類變數無法更改。但是,如果它們引用物件,則可以更改物件的屬性。

預設值

[編輯 | 編輯原始碼]

如果函式呼叫時傳遞的引數少於其包含的引數,則多餘的引數保持未定義。但是,您可以透過在函式簽名中分配一個值來定義此情況的預設值。缺失的引數將獲得這些值作為其預設值。

"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

可變數量的引數

[編輯 | 編輯原始碼]

對於某些函式,它們接受不同數量的引數是“正常”的。例如,考慮一個顯示姓名的函式。firstNamefamilyName 在任何情況下都必須給出,但也可以顯示 academicTitletitleOfNobility。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"));

呼叫中的第三個引數以及所有後續引數將收集到一個數組中,該陣列在函式中作為最後一個引數可用。這允許使用迴圈並簡化函式的原始碼。

“arguments”關鍵字

[編輯 | 編輯原始碼]

與 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 <expression>,其中<expression> 為可選。

函式執行,直到它遇到這樣的return 語句(或者發生未捕獲的異常,或者在最後一個語句之後)。<expression> 可以是一個簡單的任何資料型別的變數,例如return 5,或者是一個複雜的表示式,例如return myString.length,或者完全省略:return

如果return 語句中沒有<expression>,或者根本沒有達到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 的上一次呼叫,... 。

... 在另一個頁面上提供(點選此處)。

另請參閱

[編輯 | 編輯原始碼]
華夏公益教科書