javaScript : 運算式與運算子



寫 JavaScript,八成時間都在跟「值」打交道:拿到一個值、改一個值、比一個值。這些動作背後,主角是「運算式」跟「運算子」。運算式會產生值,運算子決定怎麼算;兩者合起來,才有我們熟悉的判斷、計算、組合字串、處理資料。很多莫名其妙的 bug,像是 "5" + 1 變成 "51"、0 || 10 變 10、或 == 跟 === 判斷不一樣,其實都跟它們有關。
這篇不賣關子,直接把常見運算子的用法、差異、優先序與結合性講清楚,還會帶你避開 ??、&&、|| 混用的括號陷阱、浮點數精度、以及 NaN 的各種詭異邏輯。看完你會更敢用表達式把邏輯收斂,寫出穩定、好維護的程式。希望本篇文章能夠幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼先搞懂「運算式」與「運算子」?

寫 JS 的每一行,多半都是在建立值、變換值、或判斷值。這三件事,幾乎都靠「運算式」與「運算子」完成。

運算式(Expression):會產生「值」的任何語法片段。

例:3 + 4、user.name、getPrice()、a ?? 0、isReady ? "OK" : "WAIT"。

運算子(Operator):用來運算、比較、邏輯組合等的符號或關鍵字。

例:+、-、*、/、===、&&、||、??、!、typeof、delete、in、instanceof、**…等。


理解兩者差別很關鍵:

運算式會回傳一個值(就算是副作用型的運算式,最終也有值,例如賦值表達式)。

運算子是達成運算式的工具,決定運算規則與優先順序。


一句話記:「運算式是名詞(產生值),運算子是動詞(操作值)」。


運算式 vs. 陳述式(Statement)

陳述式(Statement):執行一件事,不一定回傳值。

例:if (x > 0) {...}、for (...) {...}、return y。

運算式:一定能產生值,因此可以嵌在其他表達式或引數中。

例:const y = (x > 0 ? 1 : -1) * 10;

JS 有所謂 「表達式陳述式」(Expression Statement),像是 a = 3; 雖是陳述式,但核心是賦值「表達式」。這也是為什麼很多框架偏好「以表達式為中心」的寫法——組件的 props、函式的回傳,幾乎都靠表達式。


運算子總整理

類別 運算子 名稱 / 用途 元數 回傳/特性 範例
成員/呼叫 . 成員存取 二元 取物件屬性 user.name
成員/呼叫 [] 計算式成員存取 二元 用鍵/索引取值 arr[0]
obj[key]
成員/呼叫 () 函式呼叫 N 元 呼叫並回傳結果 fn(a,b)
成員/呼叫 new 建構 N 元 產生實例(綁定 this) new Date()
成員/呼叫 new.target 目標建構子 一元 在建構子內得知是否由 new 呼叫 function C(){
   if(!new.target) return new C();
}
成員/呼叫 super 父類/原型呼叫 N 元 取父類成員或呼叫父建構子 super.method()
super()
成員/呼叫 import() 動態匯入 一元 回傳 Promise import('./x.js')
成員/呼叫 import.meta 模組中繼資訊 一元 物件(如 URL) import.meta.url
成員/呼叫 ?. 可選鏈 二元 左側為 null/undefined 時短路回 undefined user?.profile?.name
fn?.()
obj?.[k]
分組 () 分組 一元 改變優先序 (a + b) * c
算術 + 加法 / 字串連接 二元 數學加法或字串連接 1 + 2
"a" + 1
算術 - 減法 二元 數值相減 5 - 3
算術 * 乘法 二元 數值相乘 2 * 4
算術 / 除法 二元 浮點除法 5 / 2 
算術 % 餘數 二元 同號跟隨被除數 5 % 2
算術 ** 指數 二元(右結合) 次方 2 ** 3 
算術 +x 正號
(轉數值)
一元 轉成 number +"42" 
算術 -x 負號 一元 相反數(含轉型) -"5" 
遞增遞減 ++x 前置遞增 一元 先加再回傳新值 let i=0; ++i 
遞增遞減 x++ 後置遞增 一元 先回舊值再加 let i=0; i++
遞增遞減 --x / x-- 遞減 一元 與上相反 --i、i--
指派 = 指派 二元(右結合) 回傳被指派的值 a = (b = 3)
複合指派(算術) +=
-=
*=
/=
%=
**=
算術複合指派 二元(右結合) 計算後回存 x += 2
複合指派(位移) <<=
>>=
>>>=
位移複合指派 二元 同上 x <<= 1
複合指派(位元) &=
^=
|=
位元複合指派 二元 32 位整數語意 mask &= 0b1011
複合指派(邏輯) &&=
||=
??=
邏輯/Nullish 複合指派 二元 依左值短路決定是否更新 a ||= 10
b &&= f()
c ??= 0
比較 ==
!=
寬鬆相等/
不等
二元 允許型別轉換 "5" == 5 
比較 ===
!==
嚴格相等/
不等
二元 不轉型 "5" === 5 
比較 <
<=
>
>=
大小比較 二元 依規則比較字串或數值 "2" < 10 
關係 in 屬性是否存在 二元 查物件/原型鏈鍵 "length" in []
關係 instanceof 原型鏈檢查 二元 物件是否出自
建構子原型鏈
d instanceof Date
邏輯 ! 邏輯非 一元 轉布林後取反 !0 
邏輯 && 邏輯且(短路) 二元 回其中一個操作數 a && b
(a 真回 b,否則回 a)
邏輯 || 邏輯或(短路) 二元 回其中一個操作數 a || b(
a 真回 a,否則回 b)
Null 合併 ?? Nullish 合併 二元 只在 null/undefined
時採右值
n ?? 10
位元 & AND 二元 32 位整數運算 5 & 3 
位元 | OR 二元 同上 5 | 2 
位元 ^ XOR 二元 同上 5 ^ 1
位元 ~ NOT 一元 位元反轉(等於 -(x+1)) ~5 
位元 << 左移 二元 整數左移 1 << 3 
位元 >> 右移(保符號) 二元 算術右移 -8 >> 1 
位元 >>> 無號右移 二元 高位補 0 -1 >>> 0 
型別/其他 typeof 取得型別字串 一元 回傳如 "number"、
"object"
typeof fn 
型別/其他 void 回傳 undefined 一元 丟棄結果 void 0
型別/其他 delete 刪物件自有屬性 一元 回傳布林 delete obj.key
條件 ?: 三元條件 三元 依條件回分支值 ok ? A : B
逗號 , 逗號運算子 二元(左結合) 依序計算、
回最後一個值
(x=1, y=2, y)
非同步 await 等待 Promise 一元 取解析值(僅 async 中) const v = await p
產生器 yield 產出值 一元 暫停並回傳給迭代器 yield 3
產生器 yield* 委派產出 一元 迭代另一個可迭代物 yield* arr


小備註(避免踩雷)

1.    + 只要一邊是字串,會走字串連接;要數學加法請先 Number(...)。

2.    || 會把 0/""/false 視為「不成立」而取右值;想保留這些值請用 ??。

3.    ?. 不能放在指派目標左邊(obj?.x = 1 會錯)。

4.    位元運算都以 32 位有號整數處理;大整數請改 BigInt 或字串方案。

5.    await 只能在 async 函式內;yield 只能在 function* 內。


運算子的主要分類與用途

1.    算術運算子

二元:+ 加、- 減、* 乘、/ 除、% 取餘、** 次方

5 % 2;      // 1
2 ** 3;     // 8


一元:+x 轉數值、-x 負號

+"42";      // 42(number)
-"5";       // -5(number)


2. 字串連接(+ 的雙重身份)

"Hello " + "JS"; // "Hello JS"
"5" + 1;         // "51"(字串連接,而非數學加法)


只要任一側是字串,+ 多半走字串連接。想做數學加法,請先明確轉型:Number("5") + 1 // 6。


3.     指派(賦值)運算子

基本:=

複合:+=、-=、*=、/=、%=、**=、<<=、>>=…

let n = 3;
n += 2;     // 5
n **= 2;    // 25


4.    比較運算子

寬鬆相等:==、!=(不建議)

嚴格相等:===、!==(預設使用)

大小比較:>、>=、<、<=

0 == false;   // true(型別轉換介入)
0 === false;  // false(型別不同)
"2" > 10;     // false(字串轉數值後比較)


5.    邏輯運算子與短路行為

&&(且)、||(或)、!(非)

短路(short-circuit)會直接回傳其中一個操作數,而不一定是布林:

"A" && "B";   // "B"(因為左邊 truthy,回右邊)
"" || "B";    // "B"(左邊 falsy,回右邊)
!0;           // true


6) Null 合併運算子 ??

只在 左側為 null 或 undefined 時才採右側預設值。

0 ?? 10;            // 0(不觸發預設)
null ?? 10;         // 10
undefined ?? "X";   // "X"

與 || 的差異:0、""、false 在 || 會被當作 falsy 而觸發預設;在 ?? 不會。


可選鏈(Optional Chaining)?.

避免存取不存在屬性時拋錯:

user?.profile?.name ?? "Guest";


注意:?. 是運算子,常與 ?? 搭配,提升健壯性。


8.    型別/物件相關

typeof x:回傳型別字串(如 "number", "object", "function")。

instanceof:判斷原型鏈。date instanceof Date

in:屬性名是否存在於物件或其原型鏈。"length" in [] // true

delete obj.key:刪除物件自有屬性(刪不掉 let/const 變數)。


9.    位元運算子(常用於效能或旗標)

&、|、^、~、<<、>>、>>>。

5 & 3;  // 1(0101 & 0011 = 0001)


10.    其餘常見

三元:cond ? A : B(把 if/else 收斂成值)

逗號:(expr1, expr2) 回傳最後一個值(少用,易讀性差)

括號 ():分組、決定優先序


展開/其餘(...):不是嚴格意義的運算子,但在表達式中常用來展開陣列/物件或收集其餘參數。


優先序與結合性(Precedence & Associativity)

不同運算子有不同優先序(誰先算)與結合性(同優先序時,從左或從右算)。

幾個實務重點:

        () 最高可讀性,不確定就加括號。

        ** 與賦值(含 =、+= 等)是右結合:a = b = 3; 會先算 b = 3。

        ?? 與 &&/|| 不可混用而不加括號:a ?? b || c 會報錯或造成閱讀困難。

        正確寫法:a ?? (b || c) 或 (a ?? b) || c,依你想要的邏輯加括號。


實作範例

範例 1:安全讀值 + 預設值

function greet(user) {
  const name = user?.profile?.name ?? "訪客";
  return `Hi, ${name}!`;
}


範例 2:輸入正規化(避免字串相加陷阱)

function add(a, b) {
  const x = Number(a);
  const y = Number(b);
  if (Number.isNaN(x) || Number.isNaN(y)) {
    throw new Error("輸入需為可轉成數字的值");
  }
  return x + y;
}


範例 3:條件運算(收斂 if/else)

const levelLabel = (score) =>
  score >= 90 ? "A" :
  score >= 80 ? "B" :
  score >= 70 ? "C" : "D";


巢狀三元式可讀性差時,改回 if/else 或抽出函式映射。


範例 4:位元旗標(啟用多個選項)

const FLAG_READ  = 1 << 0; // 0001
const FLAG_WRITE = 1 << 1; // 0010
const FLAG_EXEC  = 1 << 2; // 0100

let perm = FLAG_READ | FLAG_WRITE;  // 0011
const canExec = (perm & FLAG_EXEC) !== 0; // false


範例 5:鏈式賦值與副作用

let a, b;
a = b = 10;   // 先算 (b = 10) 回傳 10,再指派給 a


可讀性考量,不建議過度使用鏈式賦值。


一步步實作流程

步驟 1|釐清需求:你要的是什麼「值」?

先想「這段程式最後要回傳什麼值?」

把 if/else 能改寫成一個表達式就改(保留可讀性)。

步驟 2|選擇正確運算子

做數學:+ - * / % **

做判斷:=== !== > < >= <=

安全讀值:?. 搭 ??

條件分支:cond ? A : B

預設值:優先 ??,除非真的要把 ""/0/false 視為「無效」。

步驟 3|控制優先序

不要賭運算子優先序,加括號讓意圖明確,尤其 ??、&&、|| 混用時。

步驟 4|型別確認與轉換

讀取外部輸入(URL、表單、localStorage)前先轉型:Number()、String()、Boolean()。

檢查數字有效性:Number.isNaN(x) 與 Number.isFinite(x)。


步驟 5|驗證與除錯

console.log({ value, type: typeof value });


若牽涉可選鏈,刻意餵 null/undefined 測試。

對比 == 與 === 差異,固定使用 ===。


常見錯誤與雷點

1.    == 造成的陰招

"" == 0;        // true
false == 0;     // true
null == undefined; // true


建議:預設用 === / !==。


2.    + 被迫變字串連接

"5" + 1; // "51"


解法:先 Number("5") 再加;或確保兩側都是數字。


3.    NaN 的比較

NaN === NaN;          // false
Number.isNaN(NaN);    // true(正確檢查)


4.    浮點數精度

0.1 + 0.2 === 0.3;    // false


作法:以整數計算(乘上 100 後再除)或用誤差範圍比較。


5.    || 當預設值踩雷

const n = 0 || 10;    // 10(你失去合法的 0)

改用:??,只在 null/undefined 才套預設。


6.    ?? 與 &&/|| 混用未加括號

a ?? b || c;  // 問題多,請加括號

固定:(a ?? b) || c 或 a ?? (b || c)。


7.    可選鏈誤用在賦值左側

user?.name = "A"; // ❌ SyntaxError

原則:?. 只用在存取時,不能當賦值目標。


8.    delete 想刪變數

let x = 1;
delete x;      // false(刪不掉)


用途:刪「物件自有屬性」:delete obj.key。


9.    前置/後置 ++ 差異

let i = 0;
i++   // 回傳 0,再把 i 變 1
++i   // 先把 i 變 1,然後回傳 1


在表達式中會有差異,避免寫成一行巫術。


10.    parseInt 忘了基底

parseInt("08");     // 8(現代環境多半 OK,但仍建議指定 10)
parseInt("101", 2); // 5


更穩:數字字串用 Number("08")。


11.    位元運算只保留 32 位有號整數

大數或超過 2^31−1 的運算會溢位;需要大整數請用 BigInt 或字串庫。


12.    instanceof 與跨 realm

不同 iframe/執行環境的 Array 彼此 instanceof 可能為 false。

普適檢查:Array.isArray(v)、Object.prototype.toString.call(v)。


13.    三元巢狀過深

可讀性直接掛掉。改成 if/else 或字典映射。


14.    忘了括號導致優先序錯判

a + b * c 與 (a + b) * c 天差地遠。寧願多兩個括號,少一天查 bug。


實務範式:把「安全」當預設

A. 讀取深層屬性:?. + ??

const price = product?.variants?.[0]?.price ?? 0;


B. 可輸入多型別但要數學計算:顯式轉型

const total = [10, "20", "3"].reduce((s, v) => s + Number(v), 0); // 33


C. 條件輸出字串:三元 + 模板字串

const tag = isHot ? "🔥 熱門" : "一般";
const label = `商品狀態:${tag}`;


D. 容錯預設值(保留 falsy 合法值)

const page = Number(query.page ?? 1);  // 1, 2, 3...
const size = Number(query.size ?? 20); // 預設 20


快速對照表

目的 推薦寫法 備註
嚴格比較 a === b / a !== b 避免 ==
預設值(允許 0/""/false) a ?? defaultVal 只處理 null/undefined
安全取值 obj?.x?.y 不會拋錯
數字轉型 Number(v) 檢查 Number.isNaN(v)
字串相加 先轉型再 + 避免 "5"+1
浮點運算 以整數運算或誤差比較 避免 0.1+0.2 陷阱
陣列判斷 Array.isArray(v) 勝過 instanceof
刪屬性 delete obj.key 刪不掉 let/const 變數


結語:讓每一個「值」都在你掌控內

把 JS 想成「以表達式為核心」的語言:先想要的值,再選運算子,最後用括號固定邏輯。

穩定的預設是:===、??、?.、顯式轉型、必要括號。做完再用 console.log 與邊界值驗證一次。多一層確定,少一天救火。


延伸閱讀推薦:


張貼留言 (0)
較新的 較舊