現代 JavaScript 教程之函數基礎篇

函數

咱們常常須要在腳本的許多地方執行很類似的操做。javascript

例如,當訪問者登陸、註銷或者在其餘地方時,咱們須要顯示一條好看的信息。html

函數是程序的主要「構建模塊」。函數使該段代碼能夠被調用不少次,而不須要寫重複的代碼。java

咱們已經看到了內置函數的示例,如 alert(message)prompt(message, default)confirm(question)。但咱們也能夠建立本身的函數。react

函數聲明

使用 函數聲明 建立函數。jquery

看起來就像這樣:web

function showMessage() {
  alert( 'Hello everyone!' );
}
複製代碼

function 關鍵字首先出現,而後是 函數名,而後是括號之間的 參數 列表(用逗號分隔,在上述示例中爲空),最後是花括號之間的代碼(即「函數體」)。express

function name(parameters) {
  ...body...
}
複製代碼

咱們的新函數能夠經過名稱調用:showMessage()微信

例如:框架

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

showMessage();
showMessage();
複製代碼

調用 showMessage() 執行函數的代碼。這裏咱們會看到顯示兩次消息。函數

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

若是咱們須要更改消息或其顯示方式,只需在一個地方修改代碼:輸出它的函數。

局部變量

在函數中聲明的變量只在該函數內部可見。

例如:

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

  alert( message );
}

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

alert( message ); // <-- 錯誤!變量是函數的局部變量
複製代碼

外部變量

函數也能夠訪問外部變量,例如:

let userName = 'John';

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

showMessage(); // Hello, John
複製代碼

函數對外部變量擁有所有的訪問權限。函數也能夠修改外部變量。

例如:

let userName = 'John';

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

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

alert( userName ); // John 在函數調用以前

showMessage();

alert( userName ); // Bob,值被函數修改了
複製代碼

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

若是在函數內部聲明瞭同名變量,那麼函數會 遮蔽 外部變量。例如,在下面的代碼中,函數使用局部的 userName,而外部變量被忽略:

let userName = 'John';

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

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

// 函數會建立並使用它本身的 userName
showMessage();

alert( userName ); // John,未被更改,函數沒有訪問外部變量。
複製代碼

全局變量

任何函數以外聲明的變量,例如上述代碼中的外部變量 userName,都被稱爲 全局 變量。

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

減小全局變量的使用是一種很好的作法。現代的代碼有不多甚至沒有全局變量。大多數變量存在於它們的函數中。可是有時候,全局變量可以用於存儲項目級別的數據。


參數

咱們可使用參數(也稱「函數參數」)來將任意數據傳遞給函數。

在以下示例中,函數有兩個參數:fromtext

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

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

當函數在 (*)(**) 行中被調用時,給定值被複制到了局部變量 fromtext。而後函數使用它們進行計算。

這裏還有一個例子:咱們有一個變量 from,並將它傳遞給函數。請注意:函數會修改 from,但在函數外部看不到更改,由於函數修改的是複製的變量值副本:

function showMessage(from, text) {

 from = '*' + from + '*'; // 讓 "from" 看起來更優雅

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

let from = "Ann";

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

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

默認值

若是未提供參數,那麼其默認值則是 undefined

例如,以前提到的函數 showMessage(from, text) 能夠只使用一個參數調用:

showMessage("Ann");
複製代碼

那不是錯誤,這樣調用將輸出 "Ann: undefined"。這裏沒有參數 text,因此程序假定 text === undefined

若是咱們想在本示例中設定「默認」的 text,那麼咱們能夠在 = 以後指定它:

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

showMessage("Ann"); // Ann: no text given
複製代碼

如今若是 text 參數未被傳遞,它將會獲得值 "no text given"

這裏 "no text given" 是一個字符串,但它能夠是更復雜的表達式,而且只會在缺乏參數時纔會被計算和分配。因此,這也是可能的:

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() 僅在沒有給定 text 時執行
  // 其運行結果將成爲 text 的值 
}
複製代碼

默認參數的計算

在 JavaScript 中,每次函數在沒帶個別參數的狀況下被調用,默認參數會被計算出來。

在上面的例子中,每次 showMessage() 不帶 text 參數被調用時,anotherFunction() 就會被調用。


舊式默認參數

舊版本的 JavaScript 不支持默認參數。因此在大多數舊版本的腳本中,你能找到其餘設置默認參數的方法。

例如,用於 undefined 的顯式檢查:

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

  alert( from + ": " + text );
}
複製代碼

……或使用 || 運算符:

function showMessage(from, text) {
  // 若是 text 能轉爲 false,那麼 text 會獲得「默認」值
  text = text || 'no text given';
  ...
}
複製代碼

返回值

函數能夠將一個值返回到調用代碼中做爲結果。

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

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

let result = sum(1, 2);
alert( result ); // 3
複製代碼

指令 return 能夠在函數的任意位置。當執行到達時,函數中止,並將值返回給調用代碼(分配給上述代碼中的 result)。

在一個函數中可能會出現不少次 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 但沒有返回值也是可行的。但這會致使函數當即退出。

例如:

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

  alert( "Showing you the movie" ); // (*)
  // ...
}
複製代碼

在上述代碼中,若是 checkAge(age) 返回 false,那麼 showMovie 將不會運行到 alert


空值的 return 或沒有 return 的函數返回值爲 undefined

若是函數無返回值,它就會像返回 undefined 同樣:

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

alert( doNothing() === undefined ); // true
複製代碼

空值的 returnreturn 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)
  )
複製代碼

而後它就能像咱們預想的那樣正常運行了。


函數命名

函數是行爲。因此它們的名字一般是動詞。它應該簡短且儘量準確地描述函數的做用。這樣讀代碼的人就能清楚地知道這個函數的功能。

一種廣泛的作法是用動詞前綴來開始一個函數,這個前綴模糊地描述了這個動做。團隊內部必須就前綴的含義達成一致。

例如,以 "show" 開頭的函數一般會顯示某些內容。

函數以 XX 開始……

  • "get…" —— 返回一個值,
  • "calc…" —— 計算某些內容,
  • "create…" —— 建立某些內容,
  • "check…" —— 檢查某些內容並返回 boolean 值,等。

這類名字的示例:

showMessage(..)     // 顯示信息
getAge(..)          // 返回 age(gets it somehow)
calcSum(..)         // 計算求和並返回結果
createForm(..)      // 建立表格(一般會返回它)
checkPermission(..) // 檢查權限並返回 true/false
複製代碼

有了前綴,只需瞥一眼函數名,就能夠了解它的功能是什麼,返回什麼樣的值。


一個函數 —— 作一件事

一個函數應該只包含函數名所指定的功能,而不是作更多與函數名無關的功能。

兩個獨立的操做一般須要兩個函數,即便它們一般被一塊兒調用(在這種狀況下,咱們能夠建立第三個函數來調用這兩個函數)。

有幾個違反這一規則的例子:

  • getAge —— 若是它經過 alert 將 age 顯示出來,那就有問題了(只應該是獲取)。
  • createForm —— 若是它包含修改文檔的操做,例如向文檔添加一個表單,那就有問題了(只應該建立表單並返回)。
  • checkPermission —— 若是它顯示 access granted/denied 消息,那就有問題了(只應執行檢查並返回結果)。

這些例子假設函數名前綴具備通用的含義。你和你的團隊能夠自定義這些函數名前綴的含義,可是一般都沒有太大的不一樣。不管怎樣,你都應該對函數名前綴的含義、帶特定前綴的函數能夠作什麼以及不能夠作什麼有深入的瞭解。全部相同前綴的函數都應該遵照相同的規則。而且,團隊成員應該造成共識。


很是短的函數命名

經常使用的函數有時會有很是短的名字。

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

這些都是例外,通常而言,函數名應簡明扼要且具備描述性。


函數 == 註釋

函數應該簡短且只有一個功能。若是這個函數功能複雜,那麼把該函數拆分紅幾個小的函數是值得的。有時候遵循這個規則並非那麼容易,但這絕對是件好事。

一個單獨的函數不只更容易測試和調試 —— 它的存在自己就是一個很好的註釋!

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

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

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 ); // 一個素數
  }
}
複製代碼

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

function showPrimes(n) {

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

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

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

第二個變體更容易理解,不是嗎?咱們經過函數名(isPrime)就能夠看出函數的功能,而不須要經過代碼。人們一般把這樣的代碼稱爲 自描述

所以,即便咱們不打算重用它們,也能夠建立函數。函數可讓代碼結構更清晰,可讀性更強。

總結

函數聲明方式以下所示:

function name(parameters, delimited, by, comma) {
  /* code */
}
複製代碼
  • 做爲參數傳遞給函數的值,會被複制到函數的局部變量。
  • 函數能夠訪問外部變量。但它只能從內到外起做用。函數外部的代碼看不到函數內的局部變量。
  • 函數能夠返回值。若是沒有返回值,則其返回的結果是 undefined

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

與不獲取參數但將修改外部變量做爲反作用的函數相比,獲取參數、使用參數並返回結果的函數更容易理解。

函數命名:

  • 函數名應該清楚地描述函數的功能。當咱們在代碼中看到一個函數調用時,一個好的函數名可以讓咱們立刻知道這個函數的功能是什麼,會返回什麼。
  • 一個函數是一個行爲,因此函數名一般是動詞。
  • 目前有許多優秀的函數名前綴,如 create…show…get…check… 等等。使用它們來提示函數的做用吧。

函數是腳本的主要構建塊。如今咱們已經介紹了基本知識,如今咱們就能夠開始建立和使用函數了。但這只是學習和使用函數的開始。咱們將繼續學習更多函數的相關知識,更深刻地研究它們的先進特徵。

做業題

先本身作題目再看答案。

1. 是否須要 「else」?

重要程度:⭐️⭐️⭐️⭐

若是參數 age 大於 18,那麼下面的函數將返回 true

不然它將會要求進行確認,並返回確認結果:

function checkAge(age) {
  if (age > 18) {
    return true;
  } else {
    // ...
    return confirm('Did parents allow you?');
  }
}
複製代碼

若是 else 被刪除,函數的工做方式會不一樣嗎?

function checkAge(age) {
  if (age > 18) {
    return true;
  }
  // ...
  return confirm('Did parents allow you?');
}
複製代碼

這兩個變體的行爲是否有區別?

2. 使用 '?' 或者 '||' 重寫函數

重要程度:⭐️⭐️⭐️⭐

若是參數 age 大於 18,那麼下面的函數返回 true

不然它將會要求進行確認,並返回確認結果:

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

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

編寫 checkAge 的兩個變體:

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

3. 函數 min(a, b)

重要程度:⭐

寫一個返回數字 ab 中較小的那個數字的函數 min(a,b)

例如:

min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1
複製代碼

4. 函數 pow(x,n)

重要程度:⭐️⭐️⭐️⭐

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

pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...*1 = 1
複製代碼

建立一個 web 頁面,提示輸入 xn,而後返回 pow(x,n) 的運算結果。

P.S. 在這個任務中,函數應該只支持天然數 n:從 1 開始的整數。

答案:

在微信公衆號「技術漫談」後臺回覆 1-2-14 獲取本題答案。


現代 JavaScript 教程:開源的現代 JavaScript 從入門到進階的優質教程。React 官方文檔推薦,與 MDN 並列的 JavaScript 學習教程

在線免費閱讀:zh.javascript.info


掃描下方二維碼,關注微信公衆號「技術漫談」,訂閱更多精彩內容。

相關文章
相關標籤/搜索