비트 연산자 완전 정복
JavaScript 비트 AND/OR/XOR/NOT, 시프트 연산자의 32비트 정수 변환 동작, 비트 플래그로 권한 시스템 구현, RGB 색상 처리, 2의 거듭제곱 판별 등 실전 활용 패턴을 다룹니다.
지난 글에서 ?. 옵셔널 체이닝으로 null 안전 접근을 다뤘습니다. 이번에는 JavaScript 연산자 시리즈의 마지막으로, 저수준 비트 연산자를 다룹니다. 웹 프론트엔드에서는 자주 쓰이지 않지만, 권한 시스템, 이미지 처리, 프로토콜 파싱, 성능 최적화에서 비트 연산은 강력한 도구가 됩니다.
비트 연산의 기본 원리
JavaScript의 비트 연산자는 내부적으로 피연산자를 32비트 부호 있는 정수로 변환 후 비트 단위로 연산하고, 결과를 다시 JavaScript 숫자로 변환합니다. 소수점 이하는 버려집니다.
3.9 | 0; // 3 (소수점 제거, Math.trunc와 유사)
-3.9 | 0; // -3
~~3.9; // 3 (이중 NOT: 정수 변환 관용구)
비트 AND (&)
두 비트가 모두 1일 때만 1을 반환합니다. 특정 비트가 켜져 있는지 마스킹(masking) 으로 확인할 때 사용합니다.
5 & 3; // 1
// 5 = 0101
// 3 = 0011
// & = 0001 = 1
// 홀수/짝수 판별 (가장 빠른 방법)
n & 1; // 1이면 홀수, 0이면 짝수
// 마스크로 하위 4비트 추출
value & 0b1111; // 하위 4비트만 남김
비트 OR (|)
두 비트 중 하나라도 1이면 1을 반환합니다. 특정 비트를 켜는(set) 데 사용합니다.
5 | 3; // 7
// 5 = 0101
// 3 = 0011
// | = 0111 = 7
// 비트 켜기
flags |= (1 << 2); // 2번 비트 켜기
비트 XOR (^)
두 비트가 다를 때 1을 반환합니다. 특정 비트를 토글(toggle) 하거나 간단한 암호화에 사용합니다.
5 ^ 3; // 6
// 5 = 0101
// 3 = 0011
// ^ = 0110 = 6
// XOR을 이용한 변수 스왑 (추가 변수 불필요)
let a = 5, b = 3;
a ^= b; b ^= a; a ^= b; // a=3, b=5
// 비트 토글
flags ^= (1 << 2); // 2번 비트 반전
비트 NOT (~)
모든 비트를 반전합니다. 32비트 정수의 2의 보수로 인해 ~n === -(n + 1)이 됩니다.
~5; // -6 (~5 = -(5+1))
~0; // -1
~-1; // 0
고전적 관용구로 ~arr.indexOf(item) !== 0를 arr.indexOf(item) !== -1의 단축으로 사용했지만, 현대 코드에서는 arr.includes(item)이 훨씬 명확합니다.
시프트 연산자
<< 왼쪽 시프트: 지정한 비트 수만큼 왼쪽으로 이동. 2의 거듭제곱 곱셈과 같습니다.
5 << 1; // 10 (5 × 2)
5 << 2; // 20 (5 × 4)
1 << 0; // 1
1 << 1; // 2
1 << 2; // 4
1 << 3; // 8
>> 부호 유지 오른쪽 시프트: 부호 비트를 복사하면서 오른쪽 이동.
-8 >> 1; // -4 (부호 유지)
8 >> 1; // 4
>>> 부호 없는 오른쪽 시프트: 부호 비트 포함 0으로 채우며 오른쪽 이동.
-1 >>> 0; // 4294967295 (0xFFFFFFFF) — 음수를 uint32로 변환
-1 >>> 1; // 2147483647
>>> 0은 음수 해시값을 양수 uint32로 변환하는 데 자주 사용됩니다.
비트 플래그 — 권한 시스템
비트 연산의 가장 실용적인 사용 사례는 비트 플래그입니다. 여러 불리언 상태를 단일 정수에 압축합니다.
const PERM = {
READ: 1 << 0, // 0001
WRITE: 1 << 1, // 0010
EXECUTE: 1 << 2, // 0100
DELETE: 1 << 3, // 1000
};
// 권한 부여 (OR로 합산)
let user = PERM.READ | PERM.WRITE; // 0011 = 3
// 권한 확인 (AND로 마스킹)
const canRead = (user & PERM.READ) !== 0; // true
const canDelete = (user & PERM.DELETE) !== 0; // false
// 권한 추가
user |= PERM.EXECUTE; // 0111 = 7
// 권한 제거 (AND NOT)
user &= ~PERM.WRITE; // 0101 = 5
// 권한 토글
user ^= PERM.READ; // 0100 = 4 (READ 제거)
이 패턴은 Unix 파일 권한(rwx), React의 Fiber 작업 플래그, 많은 게임 엔진 상태 관리에서 실제로 사용됩니다.
색상 처리와 비트 연산
웹 색상(0xFF5733)을 R, G, B 채널로 분해하거나 합성할 때 비트 연산이 명확합니다.
const color = 0xFF5733; // R=255, G=87, B=51
const r = (color >> 16) & 0xFF; // 255
const g = (color >> 8) & 0xFF; // 87
const b = color & 0xFF; // 51
// 합성
const packed = (r << 16) | (g << 8) | b; // 0xFF5733
성능 주의사항
비트 연산은 정수 연산이므로 이론적으로 빠르지만, 현대 JavaScript 엔진(V8, SpiderMonkey)은 32비트 변환 오버헤드가 있어 일반 산술 연산보다 느릴 수 있습니다. n & 1 대신 n % 2 === 0, ~~n 대신 Math.trunc(n)이 더 가독성이 좋고 성능도 유사합니다. 비트 연산은 프로토콜 파싱, 플래그 관리 등 비트 수준의 의미가 명확한 곳에서 사용하세요.
지난 글: 옵셔널 체이닝 ?.
읽어주셔서 감사합니다. 😊