本文 AI 產出,尚未審核
JavaScript 位元運算子(Operators)深入探討
簡介
在日常的前端開發中,我們大多使用算術運算子(+、-、*、/)或比較運算子(===、!==)來處理資料。但在處理底層資料、效能敏感的演算法或是需要直接操控二進位資料時,位元運算子(Bitwise Operators)則是不可或缺的工具。
位元運算子直接對 32 位元的二進位表示進行操作,執行速度極快,且常用於:
- 旗標(Flag)管理:使用位元來同時保存多個布林狀態。
- 加密/解密:簡易的 XOR 加密、位元旋轉等。
- 圖形與音訊處理:像素合成、色彩通道分離等。
本篇文章將從概念、語法、實作範例一路帶你了解 JavaScript 的位元運算子,並提供常見陷阱與最佳實踐,幫助你在實務中安全、有效地運用這些工具。
核心概念
1. 位元運算子概覽
| 運算子 | 說明 | 範例 (10) |
|---|---|---|
& |
位元 AND,兩位皆為 1 時結果為 1 | 10 & 6 // 0010 |
| |
位元 OR,兩位至少有一個 1 時結果為 1 | 10 | 6 // 1110 |
^ |
位元 XOR,兩位不同時結果為 1 | 10 ^ 6 // 1100 |
~ |
位元 NOT,對每一位取反 | ~10 // -11 |
<< |
左移,將二進位左移 n 位,右側補 0 | 5 << 2 // 20 |
>> |
右移(保留符號位),左側補原符號位 | -8 >> 2 // -2 |
>>> |
零填充右移,左側永遠補 0(不保留符號) | -8 >>> 2 // 1073741822 |
注意:在 JavaScript 中,所有位元運算都會先把操作數 轉成 32 位有號整數(
ToInt32),因此浮點數會被截斷。
2. 兩補數(Two's Complement)
負數在位元層面的表示方式是 兩補數。簡單說,負數的二進位等於其絕對值的二進位取反(~),再加 1。了解兩補數可幫助你解讀 ~、>>、>>> 的行為。
// 10 的二進位 (32 位) : 00000000 00000000 00000000 00001010
// ~10 的結果 : 11111111 11111111 11111111 11110101 (等於 -11)
console.log(~10); // -11
3. 位元與布林值的關係
在 JavaScript 中,0 代表 false,非 0 代表 true。因此位元運算常與布林值結合,用來檢查或設定特定位元。
const FLAG_READ = 1 << 0; // 0001
const FLAG_WRITE = 1 << 1; // 0010
const FLAG_EXEC = 1 << 2; // 0100
let permission = FLAG_READ | FLAG_WRITE; // 0011
// 檢查是否有寫入權限
const canWrite = (permission & FLAG_WRITE) !== 0;
console.log(canWrite); // true
4. 常見的位元技巧
| 技巧 | 說明 | 範例 |
|---|---|---|
| 設定旗標 | `value | = FLAG` |
| 清除旗標 | value &= ~FLAG |
perm &= ~FLAG_WRITE; |
| 切換旗標 | value ^= FLAG |
perm ^= FLAG_READ; |
| 檢查是否為偶數 | num & 1 為 0 時為偶數 |
if ((num & 1) === 0) … |
| 快速乘除 2 的冪 | 左右移位 | x << 3 等於 x * 8 |
程式碼範例
以下提供 5 個實務上常見且易於理解的範例,說明每個位元運算子的用法與注意點。
範例 1:旗標(Flag)管理
// 定義三個權限旗標
const READ = 1 << 0; // 0001
const WRITE = 1 << 1; // 0010
const EXEC = 1 << 2; // 0100
let userPerm = 0; // 初始無任何權限
// 設定 READ 與 EXEC 權限
userPerm |= READ | EXEC; // 0101
// 檢查是否具備 WRITE 權限
const hasWrite = (userPerm & WRITE) !== 0;
console.log('hasWrite?', hasWrite); // false
// 移除 EXEC 權限
userPerm &= ~EXEC; // 0001
console.log('new perm:', userPerm.toString(2).padStart(4, '0')); // 0001
範例 2:快速判斷奇偶
function isEven(num) {
// 只要最低位是 0 就是偶數
return (num & 1) === 0;
}
function isOdd(num) {
return (num & 1) === 1;
}
console.log(isEven(42)); // true
console.log(isOdd(42)); // false
範例 3:XOR 加密(簡易)
// 使用單一位元鍵值做 XOR 加密/解密
function xorCipher(text, key) {
const result = [];
for (let i = 0; i < text.length; i++) {
// 文字的 Unicode 轉成 16 位元,與 key XOR 後再轉回字元
result.push(String.fromCharCode(text.charCodeAt(i) ^ key));
}
return result.join('');
}
const secret = xorCipher('Hello World!', 0x5A);
console.log('cipher:', secret); // 加密後的亂碼
console.log('plain :', xorCipher(secret, 0x5A)); // 再次 XOR 復原
範例 4:32 位元的顏色通道分離
// 32 位元 ARGB 顏色值: 0xAARRGGBB
function splitColor(argb) {
const a = (argb >>> 24) & 0xFF;
const r = (argb >>> 16) & 0xFF;
const g = (argb >>> 8) & 0xFF;
const b = argb & 0xFF;
return { a, r, g, b };
}
const color = 0xFF3366CC; // 不透明、R=0x33、G=0x66、B=0xCC
console.log(splitColor(color)); // { a: 255, r: 51, g: 102, b: 204 }
範例 5:零填充右移處理大於 2^31 的正整數
// JavaScript 內部使用有號 32 位元,直接右移會保留符號位
let large = 0xF0000000; // 4026531840 (超過 2^31)
console.log(large >> 4); // -268435456 (保留符號)
console.log(large >>> 4); // 268435456 (正確的零填充右移)
常見陷阱與最佳實踐
| 陷阱 | 描述 | 建議的解決方式 |
|---|---|---|
| 自動轉型為 32 位有號整數 | 浮點數、BigInt、字串會被隱式轉成 32 位整數,可能導致意外的截斷。 |
在使用前顯式轉型(>>> 0)或檢查類型。 |
負數右移的符號位延伸 (>>) |
右移負數會保留負號,結果不符合「除以 2 的次方」的直覺。 | 若需要純粹的除以 2 的次方,使用 >>> 再自行處理符號。 |
| 位元運算不支援 64 位以上 | JavaScript 位元運算只能處理 32 位,對大於 2^31-1 的正整數會失真。 |
需要更大位元時,使用 BigInt 搭配自訂演算法或第三方函式庫。 |
| 優先順序混淆 | 位元運算子優先級低於算術運算子,易產生錯誤的運算結果。 | 加上括號明確運算順序,或參考 MDN 的運算子優先表。 |
使用 ~ 取反時的負數 |
~num 會得到 -(num+1),常被誤以為是「正數取反」的結果。 |
若僅想要 位元翻轉(例如 8 位元),先 & 0xFF 再使用 ~,最後再 & 0xFF。 |
最佳實踐
- 保持可讀性:使用具意義的常數(如
FLAG_READ)取代直接寫數字。 - 用括號明確運算:
(value & MASK) !== 0比value & MASK !== 0更不易出錯。 - 避免在高階邏輯中混用位元與布林:雖然
0/1可直接當布林,但寫清楚!== 0能提升可讀性。 - 測試邊界值:特別是負數、超過 2^31 的正整數,確保行為符合預期。
- 考慮未來的可維護性:若旗標集合會擴增,建議使用 Enum 或 Object.freeze 方式管理。
實際應用場景
| 場景 | 為什麼使用位元運算子 | 範例簡述 |
|---|---|---|
| 權限系統 | 只需 1 個整數即可儲存多個布林權限,省記憶體且查詢 O(1)。 | 文章管理平台的 READ、WRITE、DELETE 權限。 |
| 資料壓縮 | 把多個 0/1 狀態打包成單一位元,減少網路傳輸量。 | WebSocket 訊息的 flag 位元欄位。 |
| 圖形渲染 | 直接操作像素的 RGBA 通道,速度比使用陣列操作快。 | Canvas 中的 ImageData 逐像素處理。 |
| 加密/雜湊 | XOR、循環位移(rotate)是許多簡易加密演算法的基礎。 | 簡易的「一次性密碼」生成器。 |
| 演算法優化 | 用位元技巧取代除法或乘法(如 x << 3 代替 x * 8)提升效能。 |
大型資料集合的排序或分組。 |
總結
位元運算子是 JavaScript 中高效且功能強大的工具,雖然在日常 UI 開發裡不常見,但在需要底層控制、效能優化或多旗標管理的情境下,掌握它們會讓你的程式碼更簡潔、執行更快速。
本文從概念、語法、實作範例、常見陷阱與最佳實踐,最後列出實務應用場景,提供了完整的學習路徑。建議在實際專案中先從旗標管理或奇偶判斷等簡單案例練習,逐步擴展到顏色通道分離、XOR 加密等較高階的應用。
只要保持清晰的意圖、嚴謹的測試,位元運算子將成為你 JavaScript 工具箱裡不可或缺的一員。祝你在程式世界裡「位」元不移,寫出更佳的程式碼!