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'); 後面的程式碼永遠不會執行。 |
移除或放在條件判斷裡。 |
| 過度使用逗號運算子 | 雖然合法,但會降低可讀性。 | 只在簡單情況下使用,複雜邏輯請拆成多行語句。 |
最佳實踐
- 盡量讓表達式保持簡潔:一行只做一件事,讓值的來源一目了然。
- 使用
const取代let(如果值不會變動),這樣能讓編譯器更易優化。 - 在需要返回值的地方使用表達式,例如三元運算子、箭頭函式的隱式回傳。
- 避免在同一行混合多個語句,例如
if (a) doSomething(); else doOther();建議分行寫。 - 善用 ESLint 規則:
no-confusing-arrow、no-extra-semi、no-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 的旅程中,玩得開心、寫得順手! 🚀