本文 AI 產出,尚未審核

JavaScript 語法基礎:表達式 vs 語句


簡介

在學習 JavaScript 時,**表達式(Expression)語句(Statement)**是兩個最基礎卻最容易混淆的概念。它們決定了程式碼在執行時是否會產生值、是否能被嵌入其他語法結構,以及如何影響程式的控制流程。掌握兩者的差異,不僅能寫出更簡潔、可讀性更高的程式,也能避免許多微妙的錯誤,對於從初學者晉升為中級開發者尤為關鍵。

本篇文章將以 繁體中文(台灣) 為語言,深入說明表達式與語句的定義、相互關係、實務應用,以及常見的陷阱與最佳實踐,讓你在撰寫 JavaScript 時能更得心應手。


核心概念

1. 什麼是表達式?

表達式 是一段程式碼,會產生一個值(value),並且可以在其他表達式或語句中被使用。

常見的表達式類型包括:

類型 範例 產生的值
字面量 42"Hello"true 本身的值
變數參照 count 變數目前的值
算術運算 a + b * 2 數值結果
函式呼叫 Math.max(3, 7) 函式回傳值
條件運算子 age >= 18 ? "成人" : "未成年" 三元運算結果
物件/陣列字面量 { name: "Tom" }[1,2,3] 產生物件或陣列

重點:只要能「產生」值,就算是表達式。

2. 什麼是語句?

語句 是一段程式碼,執行某種行為,但不一定會回傳值。語句通常用來控制流程、宣告變數、或執行副作用(side‑effect)。

常見的語句類型包括:

類型 範例 目的
宣告語句 let x = 10; 宣告變數或常數
條件語句 if (x > 0) { … } 控制分支
迴圈語句 for (let i=0; i<5; i++) { … } 重複執行
函式宣告 function foo() { … } 定義函式
返回語句 return x * 2; 從函式回傳值
拋出語句 throw new Error('Oops'); 產生例外

重點:語句本身不一定會產生值,但可以包含表達式(例如 let y = a + b; 中的 a + b 是表達式,整行則是宣告語句)。

3. 表達式可以嵌入語句,語句則不可以直接嵌入表達式

// ✅ 表達式嵌入語句
let sum = a + b;          // a + b 為表達式,整行為宣告語句

// ❌ 語句嵌入表達式(語法錯誤)
let result = if (x > 0) { x } else { -x }; // 錯誤:if 是語句,不能直接當作表達式使用

若需要在表達式環境使用條件邏輯,必須改用 條件運算子(?:),它本身就是一個表達式。

4. 表達式與語句的執行結果

程式碼 類型 產生的值 是否可作為其他表達式的一部份
5 * 3 表達式 15
let x = 5; 語句 undefined ❌(不能直接放在其他表達式中)
x > 0 ? "正" : "負" 表達式 "正""負"
return x; 語句 (結束函式)

程式碼範例

以下示範 5 個實用範例,說明表達式與語句的互動與應用。

範例 1:算術表達式 + 宣告語句

let a = 8;               // 宣告語句,變數 a 被初始化為 8
let b = 3;               // 宣告語句
let product = a * b;     // a * b 為算術表達式,結果被賦值給 product(也是宣告語句)
console.log(product);   // => 24

說明a * b 是表達式,let product = ... 是語句。

範例 2:條件運算子(表達式)在函式回傳

function classifyAge(age) {
  // age >= 18 ? "成人" : "未成年" 為條件運算子表達式
  return age >= 18 ? "成人" : "未成年";
}

console.log(classifyAge(20)); // => "成人"
console.log(classifyAge(15)); // => "未成年"

說明return 是語句,後面的三元運算子是表達式,直接作為回傳值。

範例 3:以 IIFE(立即執行函式)產生值

// IIFE 本身是一個函式呼叫表達式,會回傳一個物件
const config = (function () {
  const env = "production";
  const debug = false;
  return { env, debug };   // 物件字面量是表達式
})();

console.log(config); // => { env: "production", debug: false }

說明:整段 IIFE 是一個 函式呼叫表達式,結果(物件)被賦值給 config,因此可以在宣告語句中使用。

範例 4:在迴圈中使用表達式

const numbers = [1, 2, 3, 4, 5];
let sum = 0;                       // 宣告語句

for (let i = 0; i < numbers.length; i++) {   // for 迴圈語句
  sum += numbers[i];               // numbers[i] 為表達式,sum += ... 為賦值表達式
}
console.log(sum); // => 15

說明numbers[i] 取值是表達式,sum += ... 同樣是表達式,整個 for 卻是語句。

範例 5:利用逗號運算子(表達式)一次執行多個副作用

let x = 0, y = 0;   // 逗號分隔的宣告語句

// 逗號運算子是一個表達式,會依序求值並回傳最後一個值
x = (y = 5, y * 2); // 先把 y 設為 5,接著回傳 y*2 (=10) 給 x

console.log(x); // => 10
console.log(y); // => 5

說明(y = 5, y * 2)逗號運算子表達式,可在單一語句中完成多個動作。


常見陷阱與最佳實踐

陷阱 說明 解決方式
把語句當作表達式使用 例如 if (x) { y = 1 } else { y = 2 } 直接放在賦值右側會報錯。 改用條件運算子 x ? 1 : 2,或將 if 包在 IIFE 中。
忘記分號導致自動插入 (ASI) 產生意外表達式 let a = b\n(c).doSomething() 會被解析成 let a = b(c).doSomething() 在每行結尾加上分號,或使用括號明確分隔。
在 const 宣告後忘記賦值 const foo; 會拋 SyntaxError,因為 const 必須在宣告時初始化。 直接寫 const foo = expression;
在函式內部使用 return 後仍寫其他語句 return x; console.log('never'); 後面的程式碼永遠不會執行。 移除或放在條件判斷裡。
過度使用逗號運算子 雖然合法,但會降低可讀性。 只在簡單情況下使用,複雜邏輯請拆成多行語句。

最佳實踐

  1. 盡量讓表達式保持簡潔:一行只做一件事,讓值的來源一目了然。
  2. 使用 const 取代 let(如果值不會變動),這樣能讓編譯器更易優化。
  3. 在需要返回值的地方使用表達式,例如三元運算子、箭頭函式的隱式回傳。
  4. 避免在同一行混合多個語句,例如 if (a) doSomething(); else doOther(); 建議分行寫。
  5. 善用 ESLint 規則no-confusing-arrowno-extra-semino-unexpected-multiline 等可防止常見的表達式/語句混淆。

實際應用場景

1. UI 渲染時的條件顯示

在 React、Vue 等框架中,JSX / 模板 常使用表達式來決定要渲染什麼:

{/* JSX 中的花括號內必須是表達式 */}
<div>{isLoggedIn ? <Dashboard /> : <LoginForm />}</div>

此處的三元運算子是表達式,直接作為子節點回傳。

2. 資料庫查詢建構

使用函式鏈式呼叫(每個呼叫都是表達式)組合查詢條件:

const query = db
  .select('id', 'name')
  .where('age', '>', 18)
  .orderBy('name')
  .limit(10);   // 每一步都是表達式,最終返回 query 物件

3. 中介層(Middleware)中的早期返回

在 Express.js 中,return 語句 常用於阻止後續中介層執行:

app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).send('Unauthorized'); // return 為語句
  }
  next(); // 只有授權時才會執行
});

4. 計算屬性(Computed Property)

在物件字面量內使用計算屬性名稱(表達式):

const prefix = 'user';
const settings = {
  [prefix + 'Name']: 'Alice', // [] 內是表達式
  [prefix + 'Age']: 30
};

5. 動態匯入(Dynamic Import)

import() 本身是一個函式呼叫表達式,可在條件語句內使用:

if (type === 'chart') {
  import('./chart.js')
    .then(module => module.renderChart())
    .catch(console.error);
}

總結

  • 表達式:產生值,可嵌入其他表達式或語句之中。
  • 語句:執行動作(宣告、控制流程、回傳等),不一定回傳值,但可以包含表達式。
  • 正確區分兩者能讓程式碼更易讀、避免語法錯誤,同時提升效能與維護性。

在日常開發中,把能寫成表達式的地方盡量寫成表達式(如三元運算子、箭頭函式隱式回傳),而需要控制流程或副作用時則使用語句(if、for、return、throw 等)。配合 ESLint 及嚴謹的程式碼風格,能讓你的 JavaScript 程式更加穩定、可預測。

祝你在 JavaScript 的旅程中,玩得開心、寫得順手! 🚀