Javascript 函數(shù)

2023-02-17 10:37 更新

我們經(jīng)常需要在腳本的許多地方執(zhí)行很相似的操作。

例如,當訪問者登錄、注銷或者在其他地方時,我們需要顯示一條好看的信息。

函數(shù)是程序的主要“構(gòu)建模塊”。函數(shù)使該段代碼可以被調(diào)用很多次,而不需要寫重復的代碼。

我們已經(jīng)看到了內(nèi)建函數(shù)的示例,如 alert(message)、prompt(message, default) 和 confirm(question)。但我也可以創(chuàng)建自己的函數(shù)。

函數(shù)聲明

使用 函數(shù)聲明 創(chuàng)建函數(shù)。

看起來就像這樣:

function showMessage() {
  alert( 'Hello everyone!' );
}

function 關(guān)鍵字首先出現(xiàn),然后是 函數(shù)名,然后是括號之間的 參數(shù) 列表(用逗號分隔,在上述示例中為空,我們將在接下來的示例中看到),最后是花括號之間的代碼(即“函數(shù)體”)。

function name(parameter1, parameter2, ... parameterN) {
  ...body...
}

我們的新函數(shù)可以通過名稱調(diào)用:showMessage()。

例如:

function showMessage() {
  alert( 'Hello everyone!' );
}

showMessage();
showMessage();

調(diào)用 showMessage() 執(zhí)行函數(shù)的代碼。這里我們會看到顯示兩次消息。

這個例子清楚地演示了函數(shù)的主要目的之一:避免代碼重復。

如果我們需要更改消息或其顯示方式,只需在一個地方修改代碼:輸出它的函數(shù)。

局部變量

在函數(shù)中聲明的變量只在該函數(shù)內(nèi)部可見。

例如:

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // 局部變量

  alert( message );
}

showMessage(); // Hello, I'm JavaScript!

alert( message ); // <-- 錯誤!變量是函數(shù)的局部變量

外部變量

函數(shù)也可以訪問外部變量,例如:

let userName = 'John';

function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}

showMessage(); // Hello, John

函數(shù)對外部變量擁有全部的訪問權(quán)限。函數(shù)也可以修改外部變量。

例如:

let userName = 'John';

function showMessage() {
  userName = "Bob"; // (1) 改變外部變量

  let message = 'Hello, ' + userName;
  alert(message);
}

alert( userName ); // John 在函數(shù)調(diào)用之前

showMessage();

alert( userName ); // Bob,值被函數(shù)修改了

只有在沒有局部變量的情況下才會使用外部變量。

如果在函數(shù)內(nèi)部聲明了同名變量,那么函數(shù)會 遮蔽 外部變量。例如,在下面的代碼中,函數(shù)使用局部的 userName,而外部變量被忽略:

let userName = 'John';

function showMessage() {
  let userName = "Bob"; // 聲明一個局部變量

  let message = 'Hello, ' + userName; // Bob
  alert(message);
}

// 函數(shù)會創(chuàng)建并使用它自己的 userName
showMessage();

alert( userName ); // John,未被更改,函數(shù)沒有訪問外部變量。

全局變量

任何函數(shù)之外聲明的變量,例如上述代碼中的外部變量 userName,都被稱為 全局 變量。

全局變量在任意函數(shù)中都是可見的(除非被局部變量遮蔽)。

減少全局變量的使用是一種很好的做法?,F(xiàn)代的代碼有很少甚至沒有全局變量。大多數(shù)變量存在于它們的函數(shù)中。但是有時候,全局變量能夠用于存儲項目級別的數(shù)據(jù)。

參數(shù)

我們可以通過參數(shù)將任意數(shù)據(jù)傳遞給函數(shù)。

在如下示例中,函數(shù)有兩個參數(shù):from 和 text。

function showMessage(from, text) { // 參數(shù):from 和 text
  alert(from + ': ' + text);
}

showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)

當函數(shù)在 (*) 和 (**) 行中被調(diào)用時,給定值被復制到了局部變量 from 和 text。然后函數(shù)使用它們進行計算。

這里還有一個例子:我們有一個變量 from,并將它傳遞給函數(shù)。請注意:函數(shù)會修改 from,但在函數(shù)外部看不到更改,因為函數(shù)修改的是復制的變量值副本:

function showMessage(from, text) {

  from = '*' + from + '*'; // 讓 "from" 看起來更優(yōu)雅

  alert( from + ': ' + text );
}

let from = "Ann";

showMessage(from, "Hello"); // *Ann*: Hello

// "from" 值相同,函數(shù)修改了一個局部的副本。
alert( from ); // Ann

當一個值被作為函數(shù)參數(shù)(parameter)傳遞時,它也被稱為 參數(shù)(argument)。

換一種方式,我們把這些術(shù)語搞清楚:

  • 參數(shù)(parameter)是函數(shù)聲明中括號內(nèi)列出的變量(它是函數(shù)聲明時的術(shù)語)。
  • 參數(shù)(argument)是調(diào)用函數(shù)時傳遞給函數(shù)的值(它是函數(shù)調(diào)用時的術(shù)語)。

我們聲明函數(shù)時列出它們的參數(shù)(parameters),然后調(diào)用它們傳遞參數(shù)(arguments)。

在上面的例子中,我們可以說:“函數(shù) showMessage 被聲明,并且?guī)в袃蓚€參數(shù)(parameters),隨后它被調(diào)用,兩個參數(shù)(arguments)分別為 from 和 "Hello"”。

默認值

如果一個函數(shù)被調(diào)用,但有參數(shù)(argument)未被提供,那么相應的值就會變成 undefined。

例如,之前提到的函數(shù) showMessage(from, text) 可以只使用一個參數(shù)(argument)調(diào)用:

showMessage("Ann");

那不是錯誤,這樣調(diào)用將輸出 "*Ann*: undefined"。因為參數(shù) text 的值未被傳遞,所以變成了 undefined。

我們可以使用 = 為函數(shù)聲明中的參數(shù)指定所謂的“默認”(如果對應參數(shù)的值未被傳遞則使用)值:

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}

showMessage("Ann"); // Ann: no text given

現(xiàn)在如果 text 參數(shù)未被傳遞,它將會得到值 "no text given"。

這里 "no text given" 是一個字符串,但它可以是更復雜的表達式,并且只會在缺少參數(shù)時才會被計算和分配。所以,這也是可能的:

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() 僅在沒有給定 text 時執(zhí)行
  // 其運行結(jié)果將成為 text 的值
}

默認參數(shù)的計算

在 JavaScript 中,每次函數(shù)在沒帶個別參數(shù)的情況下被調(diào)用,默認參數(shù)會被計算出來。

在上面的例子中,如果傳遞了參數(shù) text,那么 anotherFunction() 就不會被調(diào)用。

如果沒傳遞參數(shù) text,那么 anotherFunction() 就會被調(diào)用。

在 JavaScript 老代碼中的默認參數(shù)

幾年前,JavaScript 不支持默認參數(shù)的語法。所以人們使用其他方式來設(shè)置默認參數(shù)。

如今,我們會在舊代碼中看到它們。

例如,顯式地檢查 undefined

function showMessage(from, text) {
  if (text === undefined) {
    text = 'no text given';
  }

  alert( from + ": " + text );
}

……或者使用 || 運算符:

function showMessage(from, text) {
  // 如果 text 的值為假值,則分配默認值
  // 這樣賦值 text == "" 與 text 無值相同
  text = text || 'no text given';
  ...
}

后備的默認參數(shù)

有些時候,將參數(shù)默認值的設(shè)置放在函數(shù)執(zhí)行(相較更后期)而不是函數(shù)聲明時,也行得通。

我們可以通過將參數(shù)與 undefined 進行比較,來檢查該參數(shù)是否在函數(shù)執(zhí)行期間被傳遞進來:

function showMessage(text) {
  // ...

  if (text === undefined) { // 如果參數(shù)未被傳遞進來
    text = 'empty message';
  }

  alert(text);
}

showMessage(); // empty message

……或者我們可以使用 || 運算符:

function showMessage(text) {
  // 如果 text 為 undefined 或者為假值,那么將其賦值為 'empty'
  text = text || 'empty';
  ...
}

現(xiàn)代 JavaScript 引擎支持 空值合并運算符 ??,它在大多數(shù)假值(例如 0)應該被視為“正常值”時更具優(yōu)勢:

function showCount(count) {
  // 如果 count 為 undefined 或 null,則提示 "unknown"
  alert(count ?? "unknown");
}

showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

返回值

函數(shù)可以將一個值返回到調(diào)用代碼中作為結(jié)果。

最簡單的例子是將兩個值相加的函數(shù):

function sum(a, b) {
  return a + b;
}

let result = sum(1, 2);
alert( result ); // 3

指令 return 可以在函數(shù)的任意位置。當執(zhí)行到達時,函數(shù)停止,并將值返回給調(diào)用代碼(分配給上述代碼中的 result)。

在一個函數(shù)中可能會出現(xiàn)很多次 return。例如:

function checkAge(age) {
  if (age >= 18) {
    return true;
  } else {
    return confirm('Got a permission from the parents?');
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}

只使用 return 但沒有返回值也是可行的。但這會導致函數(shù)立即退出。

例如:

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

在上述代碼中,如果 checkAge(age) 返回 false,那么 showMovie 將不會運行到 alert。

空值的 ?return ?或沒有 ?return ?的函數(shù)返回值為 ?undefined?

如果函數(shù)無返回值,它就會像返回 undefined 一樣:

function doNothing() { /* 沒有代碼 */ }

alert( doNothing() === undefined ); // true

空值的 return 和 return undefined 等效:

function doNothing() {
  return;
}

alert( doNothing() === undefined ); // true

不要在 ?return ?與返回值之間添加新行

對于 return 的長表達式,可能你會很想將其放在單獨一行,如下所示:

return
 (some + long + expression + or + whatever * f(a) + f(b))

但這不行,因為 JavaScript 默認會在 return 之后加上分號。上面這段代碼和下面這段代碼運行流程相同:

return;
 (some + long + expression + or + whatever * f(a) + f(b))

因此,實際上它的返回值變成了空值。

如果我們想要將返回的表達式寫成跨多行的形式,那么應該在 return 的同一行開始寫此表達式?;蛘咧辽侔凑杖缦碌姆绞椒派献罄ㄌ枺?

return (
  some + long + expression
  + or +
  whatever * f(a) + f(b)
  )

然后它就能像我們預想的那樣正常運行了。

函數(shù)命名

函數(shù)就是行為(action)。所以它們的名字通常是動詞。它應該簡短且盡可能準確地描述函數(shù)的作用。這樣讀代碼的人就能清楚地知道這個函數(shù)的功能。

一種普遍的做法是用動詞前綴來開始一個函數(shù),這個前綴模糊地描述了這個行為。團隊內(nèi)部必須就前綴的含義達成一致。

例如,以 "show" 開頭的函數(shù)通常會顯示某些內(nèi)容。

函數(shù)以 XX 開始……

  • ?"get…"? —— 返回一個值,
  • ?"calc…"? —— 計算某些內(nèi)容,
  • ?"create…"? —— 創(chuàng)建某些內(nèi)容,
  • ?"check…"? —— 檢查某些內(nèi)容并返回 boolean 值,等。

這類名字的示例:

showMessage(..)     // 顯示信息
getAge(..)          // 返回 age(gets it somehow)
calcSum(..)         // 計算求和并返回結(jié)果
createForm(..)      // 創(chuàng)建表單(通常會返回它)
checkPermission(..) // 檢查權(quán)限并返回 true/false

有了前綴,只需瞥一眼函數(shù)名,就可以了解它的功能是什么,返回什么樣的值。

一個函數(shù) —— 一個行為

一個函數(shù)應該只包含函數(shù)名所指定的功能,而不是做更多與函數(shù)名無關(guān)的功能。

兩個獨立的行為通常需要兩個函數(shù),即使它們通常被一起調(diào)用(在這種情況下,我們可以創(chuàng)建第三個函數(shù)來調(diào)用這兩個函數(shù))。

有幾個違反這一規(guī)則的例子:

  • ?getAge ?—— 如果它通過 ?alert ?將 age 顯示出來,那就有問題了(只應該是獲取)。
  • ?createForm ?—— 如果它包含修改文檔的操作,例如向文檔添加一個表單,那就有問題了(只應該創(chuàng)建表單并返回)。
  • ?checkPermission ?—— 如果它顯示 ?access granted/denied? 消息,那就有問題了(只應執(zhí)行檢查并返回結(jié)果)。

這些例子假設(shè)函數(shù)名前綴具有通用的含義。你和你的團隊可以自定義這些函數(shù)名前綴的含義,但是通常都沒有太大的不同。無論怎樣,你都應該對函數(shù)名前綴的含義、帶特定前綴的函數(shù)可以做什么以及不可以做什么有深刻的了解。所有相同前綴的函數(shù)都應該遵守相同的規(guī)則。并且,團隊成員應該形成共識。

非常短的函數(shù)命名

常用的函數(shù)有時會有非常短的名字。

例如,jQuery 框架用 $ 定義一個函數(shù)。LoDash 庫的核心函數(shù)用 _ 命名。

這些都是例外,一般而言,函數(shù)名應簡明扼要且具有描述性。

函數(shù) == 注釋

函數(shù)應該簡短且只有一個功能。如果這個函數(shù)功能復雜,那么把該函數(shù)拆分成幾個小的函數(shù)是值得的。有時候遵循這個規(guī)則并不是那么容易,但這絕對是件好事。

一個單獨的函數(shù)不僅更容易測試和調(diào)試 —— 它的存在本身就是一個很好的注釋!

例如,比較如下兩個函數(shù) showPrimes(n)。它們的功能都是輸出到 n 的 素數(shù)。

第一個變體使用了一個標簽:

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {

    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

    alert( i ); // 一個素數(shù)
  }
}

第二個變體使用附加函數(shù) isPrime(n) 來檢驗素數(shù):

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;

    alert(i);  // 一個素數(shù)
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

第二個變體更容易理解,不是嗎?我們通過函數(shù)名(isPrime)就可以看出函數(shù)的行為,而不需要通過代碼。人們通常把這樣的代碼稱為 自描述。

因此,即使我們不打算重用它們,也可以創(chuàng)建函數(shù)。函數(shù)可以讓代碼結(jié)構(gòu)更清晰,可讀性更強。

總結(jié)

函數(shù)聲明方式如下所示:

function name(parameters, delimited, by, comma) {
  /* code */
}
  • 作為參數(shù)傳遞給函數(shù)的值,會被復制到函數(shù)的局部變量。
  • 函數(shù)可以訪問外部變量。但它只能從內(nèi)到外起作用。函數(shù)外部的代碼看不到函數(shù)內(nèi)的局部變量。
  • 函數(shù)可以返回值。如果沒有返回值,則其返回的結(jié)果是 ?undefined?。

為了使代碼簡潔易懂,建議在函數(shù)中主要使用局部變量和參數(shù),而不是外部變量。

與不獲取參數(shù)但將修改外部變量作為副作用的函數(shù)相比,獲取參數(shù)、使用參數(shù)并返回結(jié)果的函數(shù)更容易理解。

函數(shù)命名:

  • 函數(shù)名應該清楚地描述函數(shù)的功能。當我們在代碼中看到一個函數(shù)調(diào)用時,一個好的函數(shù)名能夠讓我們馬上知道這個函數(shù)的功能是什么,會返回什么。
  • 一個函數(shù)是一個行為,所以函數(shù)名通常是動詞。
  • 目前有許多優(yōu)秀的函數(shù)名前綴,如 ?create…?、?show…?、?get…?、?check…? 等等。使用它們來提示函數(shù)的作用吧。

函數(shù)是腳本的主要構(gòu)建塊。現(xiàn)在我們已經(jīng)介紹了基本知識,現(xiàn)在我們就可以開始創(chuàng)建和使用函數(shù)了。但這只是學習和使用函數(shù)的開始。我們將繼續(xù)學習更多函數(shù)的相關(guān)知識,更深入地研究它們的先進特征。

任務(wù)


是否需要 “else”?

重要程度: 4

如果參數(shù) age 大于 18,那么下面的函數(shù)將返回 true。

否則它將會要求進行確認,并返回確認結(jié)果:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    // ...
    return confirm('父母允許嗎?');
  }
}

如果 else 被刪除,函數(shù)的工作方式會不同嗎?

function checkAge(age) {
  if (age > 18) {
    return true;
  }
  // ...
  return confirm('父母允許嗎?');
}

這兩個變體的行為是否有區(qū)別?


解決方案

沒有區(qū)別。

在這兩種情況下,return confirm('父母允許嗎?') 均只會在 if 條件為假時執(zhí)行。


使用 '?' 或者 '||' 重寫函數(shù)

重要程度: 4

如果參數(shù) age 大于 18,那么下面的函數(shù)返回 true。

否則它將會要求進行確認,并返回確認結(jié)果:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    return confirm('Do you have your parents permission to access this page?');
  }
}

重寫這個函數(shù)并保證效果相同,不使用 if,且只需一行代碼。

編寫 ?checkAge ?的兩個變體:

  1. 使用問號運算符 ???
  2. 使用或運算符 ?||?

解決方案

使用問號運算符 '?'

function checkAge(age) {
  return (age > 18) ? true : confirm('Did parents allow you?');
}

使用或運算符 ||(最短的變體):

function checkAge(age) {
  return (age > 18) || confirm('Did parents allow you?');
}

請注意此處不需要 age > 18 左右的括號。寫上括號是為了提高可讀性。


函數(shù) min(a, b)

重要程度: 1

寫一個返回數(shù)字 a 和 b 中較小的那個數(shù)字的函數(shù) min(a,b)。

例如:

min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1

解決方案

使用 if 的解決方案:

function min(a, b) {
  if (a < b) {
    return a;
  } else {
    return b;
  }
}

使用問號運算符 '?' 的解決方案:

function min(a, b) {
  return a < b ? a : b;
}

P.S. 在 a == b 的情況下,返回什么都無關(guān)緊要。


函數(shù) pow(x,n)

重要程度: 4

寫一個函數(shù) pow(x,n),返回 x 的 n 次方。換句話說,將 x 與自身相乘 n 次,返回最終結(jié)果。

pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...*1 = 1

創(chuàng)建一個 web 頁面,提示輸入 x 和 n,然后返回 pow(x,n) 的運算結(jié)果。

P.S. 在這個任務(wù)中,函數(shù)應該只支持自然數(shù) n:從 1 開始的整數(shù)。


解決方案

function pow(x, n) {
  let result = x;

  for (let i = 1; i < n; i++) {
    result *= x;
  }

  return result;
}

let x = prompt("x?", '');
let n = prompt("n?", '');

if (n < 1) {
  alert(`Power ${n} is not supported, use a positive integer`);
} else {
  alert( pow(x, n) );
}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號