語句和表示式有什麼不同

chuck發表於2022-07-14

前言

JavaScript中的語句和表示式有什麼不同之處?

對於這個問題,我似乎知道答案,但當我嘗試向別人解釋時,我卻語塞了。對於這個問題我有一種感覺,但無法清晰的表達出來。

我後來才意識到,這個問題極其重要。它可以說是房屋的承重牆,將有助於支撐大量的JavaScript知識。

對React開發者來說,更為如此。你不得不記住的那些JSX規則,以及總是忘記遵守的那些規則,大部分都是 語句/表示式 雙重性的結果。

在這篇文章中,我將分享我對這兩者區別的一些感悟,以及我們如何在日常工作中使用這些資訊。

表示式

從本質上來說,表示式是產生值的一段JavaScript程式碼。

下面所有的例子全部都是表示式:

  • 1 → 產生值為1
  • "hello" → 產生值為"hello"
  • 5 * 10 → 產生值為50
  • num > 100 → 產生值為true或者false
  • isHappy ? "?" : "?" → 產生值為一個emoji
  • [1, 2, 3].pop() → 產生值為3

表示式可以包含其他表示式。舉例來說,你覺得下面的JS程式碼中有多少個表示式?

(5 + 1) * 2

答案是一共有5個表示式。

具體來說,分別是以下5個:

  1. (5 + 1) * 2 ,這段程式碼本身就是表示式,產生的值為12
  2. (5 + 1) ,由於有括號,這個子表示式首先求值,並解析為6
  3. 5,單個數字本身就是表示式,因為它們產生一個值。這個表示式解析為5
  4. 1,同樣的道理,這個表示式解析為1
  5. 2,這個數字形成最後的表示式,它解析為2

語句

一個JavaScript程式是一連串的語句。每條語句都是計算機做某件事的指令。

這裡是有關JavaScript中語句的示例:

let hi = 5;
if (hi > 10) {
  // 更多語句
}
throw new Error('報錯了');

關於語句和表示式,我是這麼認為的:語句是支撐我們程式的剛性結構,而表示式則填充了細節。

語句中通常有表示式的 "插槽"。我們可以把任何我們喜歡的表示式放到這些插槽裡。

舉例來說,宣告一個具有表示式插槽的變數可以這麼做:

let hi = /* 表示式 */;

在這個插槽中,我們可以使用任何先前看到過的表示式:

let hi = 1;
let hi = "hello";
let hi = 5 * 10;
let hi = num > 100;
let hi = isHappy ? "?" : "?";
let hi = [1, 2, 3].pop();

就有效語法而言,表示式是可以互換的。如果一個語句有一個表示式插槽,我們可以把任何表示式放在那裡,程式碼就會執行。並且我們不會得到語法報錯。

也就是說,我們可能會遇到其他的問題。比如說,下面的程式碼在語法層面來說是有效的,但如果我們嘗試執行就會讓瀏覽器崩潰,因為它會導致死迴圈:

while ("hello") {
    // 因為"hello"永不改變,因此迴圈會一遍又一遍的重複,直到指令碼崩潰。
    // 語法上是有效的,但仍是有問題的。
}

便捷技巧

想知道一段JS程式碼到底是語句還是表示式嗎?試著將它列印出來吧!

console.log(/* 這裡是JS程式碼 */);

如果能夠執行,該程式碼就是表示式。如果報錯,那就是語句(當然,也有可能是非法JS)。

此外,我們甚至可以看到表示式的結果,因為會將結果列印到瀏覽器的控制檯中。

這樣可以湊效是因為任意函式的引數都必須是表示式。表示式會產生一個值,並將該值傳遞到函式中。語法並不會產生一個值,因此語句不能被用作函式的引數。

即使作為一個有經驗的開發者,我也非常依賴console.log。它真的是一個好東西。

表示式作為語句

這是一個表示式:1 + 2 + 3

如果我們建立一個只包括這個表示式的JS檔案,會發生什麼?讓我們試想把下面的內容儲存為test.js

1 + 2 + 3

該檔案中有多少個語句?0個還是1個?

事情是這樣的:表達方式不能單獨存在。它們總是語句的一部分。所以在這種情況下,我們有一個看起來像這樣的語句:

/* 表示式插槽 */

除了表示式插槽之外,該語句基本上是空的。表示式1 + 2 + 3填充了該插槽,那麼語句也就生成了。

換句話說,以下所有行都是有效的語句:

// 語句 1:
let hi = /* 表示式插槽 */;
// 語句 2:
return /* 表示式插槽 */;
// 語句 3:
if (/* 表示式插槽 */) { }
// 語句 4:
/* 表示式插槽 */

通常情況下,某些教程會錯誤地指出,表示式就是語句,但這並不完全正確。表示式和語句是不同的東西。但是語句有可能在不提供任何額外字元的情況下包裹住表示式。這就好像用透明的保鮮膜包裹住一個三明治。

語句通常以分號結尾,它標誌著語句的結束。對某些語句來說分號不是必須的,如if語句、while迴圈和函式宣告。

React中的實踐

如果你曾使用過React,你可能知道大括號{}允許我們在JSX中嵌入一些JavaScript,就像這樣:

function CountdownClock({ secondsRemaining }) {
  return (
    <div>
      Time left:
      {Math.round(secondsRemaining / 60)} minutes!
    </div>
  );
}

這就是React的神奇之處,它可以讓我們擁有JavaScript的全部能力。

但有一個問題 — 我們不能在大括號裡面放置任意JavaScript程式碼。具體來說,我們只能包括表示式,而不能包括語句。大括號本質上是在我們的JSX中建立一個表示式插槽。

如果我們嘗試在大括號內嵌入一個語句,比如說if/else語句,我們會得到錯誤:

function CountdownClock({ secondsRemaining }) {
  return (
    // ? 語法報錯
    <div>
      {if (secondsRemaining > 0) {
        `${secondsRemaining} seconds left`
      } else {
        "Time expired!"
      }}
    </div>
  );
}

這是因為語句不會產生值,只有表示式才會產生值。如果我們想在JSX中嵌入if/else邏輯,我們需要使用一個三元操作符表示式:

function CountdownClock({ secondsRemaining }) {
  return (
    // ✅ 沒問題
    <div>
      {secondsRemaining > 0
        ? `${secondsRemaining} seconds left`
        : "Time expired!"
      }
    </div>
  );
}

這似乎是一個詭異的JSX/React限制,但它實際上是一個JavaScript限制。

我想我們經常責怪React的一些看似武斷的規則,比如元件必須返回一個頂層元素。但更多的時候,React只是在警告我們一個關於JavaScript的限制。

理解語句和表示式的區別是非常重要的。我們還需要了解JSX是如何編譯成JavaScript的,以及React的排程與渲染週期是如何工作的......但是,這些話題已經超出了本篇文章的範圍。

總結

一個JavaScript程式由一連串的語句組成。每個語句都是做某件事的指令,比如說,建立一個變數,執行一個if/else條件語句,或者開始一個迴圈。

表示式產生一個值,這些值被放入語句的插槽內。表示式始終是語句的一部分,即使該語句是空的。例如,下面的程式碼在執行一個迴圈時沒有使用for語句,但它仍然包含一個”透明保鮮膜”語句:

data.forEach(item => console.log(item));

這種區別可能需要一段時間才能變得顯而易見,希望這篇文章可以幫助到你。

以上就是本文的所有內容,歡迎點贊收藏轉發~

相關文章