Welcome back! ๐ In the previous lessons, you learned about various operators. Now, let's explore bitwise operators - operators that work at the binary (bit) level.
Bitwise operators are more advanced and less commonly used than other operators, but they're powerful tools for specific use cases like working with permissions, flags, colors, and performance-critical operations.
Note: This is an advanced topic. If you're a beginner, you can skip this for now and return when you need to work with binary operations.
What are Bitwise Operators?
Bitwise operators treat their operands as 32-bit binary numbers (zeros and ones) and perform operations at the bit level. They then return the result as a standard JavaScript number.
JavaScript provides seven bitwise operators:
&- Bitwise AND|- Bitwise OR^- Bitwise XOR (Exclusive OR)~- Bitwise NOT<<- Left shift>>- Sign-propagating right shift>>>- Zero-fill right shift
// Examples of bitwise operations
console.log(5 & 3); // 1 (Bitwise AND)
console.log(5 | 3); // 7 (Bitwise OR)
console.log(5 ^ 3); // 6 (Bitwise XOR)
console.log(~5); // -6 (Bitwise NOT)
console.log(5 << 1); // 10 (Left shift)
console.log(5 >> 1); // 2 (Right shift)Understanding Binary Numbers
Before diving into bitwise operators, let's understand how binary numbers work.
Decimal to Binary Conversion
// Decimal number in binary
5 โ 0000 0101
3 โ 0000 0011
10 โ 0000 1010
// JavaScript provides methods to convert
let decimal = 5;
console.log(decimal.toString(2)); // "101" (binary representation)
// Add padding for clarity
console.log(decimal.toString(2).padStart(8, '0')); // "00000101"
// Binary to decimal
let binary = "101";
console.log(parseInt(binary, 2)); // 5Binary Number Positions
Each bit position represents a power of 2:
Position: 7 6 5 4 3 2 1 0
Value: 128 64 32 16 8 4 2 1
Bit: 0 0 0 0 0 1 0 1 = 5
โ โ
4 + 1 = 5Signed Integers in JavaScript
JavaScript uses 32-bit signed integers for bitwise operations:
- Positive numbers: 0 to 2,147,483,647 (2ยณยน - 1)
- Negative numbers: -2,147,483,648 to -1 (-2ยณยน to -1)
- Uses two's complement for negative numbers
// Positive number
5 โ 0000 0000 0000 0000 0000 0000 0000 0101
// Negative number (two's complement)
-5 โ 1111 1111 1111 1111 1111 1111 1111 1011Bitwise AND (&)
The bitwise AND operator compares each bit of two numbers. The result bit is 1 only if both corresponding bits are 1.
Truth Table
Bit A Bit B A & B
โโโโโโโโโโโโโโโโโโโโโ
0 0 0
0 1 0
1 0 0
1 1 1Rule: Both bits must be 1 to get 1.
How It Works
5 = 0000 0101
3 = 0000 0011
โโโโโโโโโ
5 & 3 = 0000 0001 = 1
// Step by step:
// Position 0: 1 & 1 = 1 โ
// Position 1: 0 & 1 = 0
// Position 2: 1 & 0 = 0
// Result: 1Syntax and Examples
console.log(5 & 3); // 1
console.log(12 & 10); // 8
console.log(7 & 4); // 4
console.log(0 & 5); // 0 (0 & anything = 0)
console.log(15 & 15); // 15 (same number)
// Binary visualization
console.log((0b1100 & 0b1010).toString(2)); // "1000" = 8Practical Use Cases
1. Check if Number is Even or Odd:
function isEven(num) {
return (num & 1) === 0; // Last bit is 0 for even numbers
}
console.log(isEven(4)); // true
console.log(isEven(5)); // false
console.log(isEven(10)); // true
// Why it works:
// Even: 4 = 0100 โ last bit is 0
// Odd: 5 = 0101 โ last bit is 12. Check if Bit is Set:
function isBitSet(number, position) {
return (number & (1 << position)) !== 0;
}
console.log(isBitSet(5, 0)); // true (5 = 0101, bit 0 is 1)
console.log(isBitSet(5, 1)); // false (5 = 0101, bit 1 is 0)
console.log(isBitSet(5, 2)); // true (5 = 0101, bit 2 is 1)3. Extract Lower Bits:
// Get last 4 bits (mask with 0xF = 1111)
let value = 0b11010101;
let lowerBits = value & 0xF; // 0b0101 = 5
console.log(lowerBits.toString(2).padStart(4, '0')); // "0101"4. Color Masking (RGB):
// RGB color stored as single number: 0xRRGGBB
let color = 0xFF5733; // Orange color
// Extract components
let red = (color & 0xFF0000) >> 16; // 0xFF = 255
let green = (color & 0x00FF00) >> 8; // 0x57 = 87
let blue = (color & 0x0000FF); // 0x33 = 51
console.log(`RGB(${red}, ${green}, ${blue})`); // RGB(255, 87, 51)Bitwise OR (|)
The bitwise OR operator compares each bit of two numbers. The result bit is 1 if either or both corresponding bits are 1.
Truth Table
Bit A Bit B A | B
โโโโโโโโโโโโโโโโโโโโโ
0 0 0
0 1 1
1 0 1
1 1 1Rule: At least one bit must be 1 to get 1.
How It Works
5 = 0000 0101
3 = 0000 0011
โโโโโโโโโ
5 | 3 = 0000 0111 = 7
// Step by step:
// Position 0: 1 | 1 = 1
// Position 1: 0 | 1 = 1
// Position 2: 1 | 0 = 1
// Result: 7Syntax and Examples
console.log(5 | 3); // 7
console.log(12 | 10); // 14
console.log(4 | 2); // 6
console.log(0 | 5); // 5 (0 | anything = anything)
console.log(15 | 15); // 15 (same number)
// Binary visualization
console.log((0b1100 | 0b1010).toString(2)); // "1110" = 14Practical Use Cases
1. Set a Specific Bit:
function setBit(number, position) {
return number | (1 << position);
}
let flags = 0b0000;
flags = setBit(flags, 0); // 0b0001 = 1
flags = setBit(flags, 2); // 0b0101 = 5
console.log(flags.toString(2).padStart(4, '0')); // "0101"2. Combine Flags/Permissions:
// Permission flags
const READ = 0b001; // 1
const WRITE = 0b010; // 2
const EXECUTE = 0b100; // 4
// Grant multiple permissions
let permissions = READ | WRITE; // 0b011 = 3
console.log(permissions); // 3
// Grant all permissions
let allPermissions = READ | WRITE | EXECUTE; // 0b111 = 7
console.log(allPermissions); // 7
// Check permission
function hasPermission(userPerms, requiredPerm) {
return (userPerms & requiredPerm) === requiredPerm;
}
console.log(hasPermission(permissions, READ)); // true
console.log(hasPermission(permissions, WRITE)); // true
console.log(hasPermission(permissions, EXECUTE)); // false3. Merge RGB Components:
function createColor(red, green, blue) {
return (red << 16) | (green << 8) | blue;
}
let color = createColor(255, 87, 51); // 0xFF5733
console.log(color.toString(16).toUpperCase()); // "FF5733"4. Round to Integer (Trick):
// Using bitwise OR with 0 to truncate decimals
console.log(3.7 | 0); // 3
console.log(-3.7 | 0); // -3
console.log(5.99 | 0); // 5
// Faster than Math.floor for positive numbers
// But be careful with large numbers (32-bit limit)Bitwise XOR (^)
The bitwise XOR (Exclusive OR) operator compares each bit of two numbers. The result bit is 1 if the bits are different.
Truth Table
Bit A Bit B A ^ B
โโโโโโโโโโโโโโโโโโโโโ
0 0 0
0 1 1
1 0 1
1 1 0Rule: Bits must be different to get 1.
How It Works
5 = 0000 0101
3 = 0000 0011
โโโโโโโโโ
5 ^ 3 = 0000 0110 = 6
// Step by step:
// Position 0: 1 ^ 1 = 0 (same)
// Position 1: 0 ^ 1 = 1 (different)
// Position 2: 1 ^ 0 = 1 (different)
// Result: 6Syntax and Examples
console.log(5 ^ 3); // 6
console.log(12 ^ 10); // 6
console.log(7 ^ 4); // 3
console.log(5 ^ 0); // 5 (XOR with 0 = same number)
console.log(5 ^ 5); // 0 (XOR with itself = 0)
// Binary visualization
console.log((0b1100 ^ 0b1010).toString(2)); // "110" = 6Unique Properties of XOR
// Property 1: XOR with itself = 0
console.log(7 ^ 7); // 0
console.log(99 ^ 99); // 0
// Property 2: XOR with 0 = same number
console.log(7 ^ 0); // 7
console.log(99 ^ 0); // 99
// Property 3: XOR is commutative and associative
console.log(5 ^ 3); // 6
console.log(3 ^ 5); // 6 (same)
console.log(5 ^ 3 ^ 2); // 4
console.log((5 ^ 3) ^ 2); // 4
console.log(5 ^ (3 ^ 2)); // 4 (same)
// Property 4: XOR twice = original
let original = 42;
let key = 123;
let encrypted = original ^ key;
let decrypted = encrypted ^ key;
console.log(decrypted === original); // truePractical Use Cases
1. Toggle a Bit:
function toggleBit(number, position) {
return number ^ (1 << position);
}
let value = 0b0101; // 5
value = toggleBit(value, 1); // 0b0111 = 7
value = toggleBit(value, 1); // 0b0101 = 5 (toggled back)
console.log(value); // 52. Simple Encryption (Very Basic):
function simpleEncrypt(text, key) {
return text.split('').map(char =>
String.fromCharCode(char.charCodeAt(0) ^ key)
).join('');
}
function simpleDecrypt(encrypted, key) {
return simpleEncrypt(encrypted, key); // XOR is symmetric!
}
let message = "Hello";
let key = 123;
let encrypted = simpleEncrypt(message, key);
let decrypted = simpleDecrypt(encrypted, key);
console.log("Original:", message); // "Hello"
console.log("Encrypted:", encrypted); // Gibberish
console.log("Decrypted:", decrypted); // "Hello"3. Swap Variables Without Temp:
let a = 5;
let b = 10;
console.log("Before:", a, b); // Before: 5 10
// Swap using XOR
a = a ^ b; // a = 5 ^ 10
b = a ^ b; // b = (5 ^ 10) ^ 10 = 5
a = a ^ b; // a = (5 ^ 10) ^ 5 = 10
console.log("After:", a, b); // After: 10 5
// Note: Modern JS has destructuring for this:
// [a, b] = [b, a];4. Find the Unique Number:
// Find the number that appears only once (all others appear twice)
function findUnique(arr) {
let result = 0;
for (let num of arr) {
result ^= num; // XOR all numbers
}
return result; // Duplicates cancel out, leaving unique
}
console.log(findUnique([2, 3, 5, 3, 2])); // 5
console.log(findUnique([1, 2, 3, 4, 3, 2, 1])); // 4
// Why it works:
// 2 ^ 3 ^ 5 ^ 3 ^ 2
// = (2 ^ 2) ^ (3 ^ 3) ^ 5
// = 0 ^ 0 ^ 5
// = 55. Check if Two Numbers Have Opposite Signs:
function haveOppositeSigns(a, b) {
return (a ^ b) < 0;
}
console.log(haveOppositeSigns(5, -3)); // true
console.log(haveOppositeSigns(5, 3)); // false
console.log(haveOppositeSigns(-5, -3)); // falseBitwise NOT (~)
The bitwise NOT operator inverts all bits (flips 1s to 0s and 0s to 1s). It's a unary operator.
How It Works
5 = 0000 0000 0000 0000 0000 0000 0000 0101
~5 = 1111 1111 1111 1111 1111 1111 1111 1010 = -6
// In JavaScript: ~n = -(n + 1)Syntax and Examples
console.log(~5); // -6
console.log(~-3); // 2
console.log(~0); // -1
console.log(~-1); // 0
// Formula: ~n = -(n + 1)
console.log(~10 === -11); // true
console.log(~99 === -100); // trueWhy ~n = -(n + 1)?
Due to two's complement representation of negative numbers:
// For number 5:
5 = 0000 0101
~5 = 1111 1010 (invert all bits)
// 1111 1010 in two's complement:
// Sign bit is 1 (negative)
// To get value: invert bits and add 1
// ~(1111 1010) = 0000 0101 = 5
// Add 1: 5 + 1 = 6
// So 1111 1010 = -6Practical Use Cases
1. Check if Index Exists (indexOf Trick):
let arr = ["apple", "banana", "orange"];
// Old way
if (arr.indexOf("banana") !== -1) {
console.log("Found");
}
// Using bitwise NOT (less common, but works)
if (~arr.indexOf("banana")) {
console.log("Found"); // ~(-1) = 0 (falsy), ~(index) is truthy
}
// Why it works:
// ~(-1) = 0 (falsy)
// ~0 = -1 (truthy)
// ~1 = -2 (truthy)
// etc.
// Note: Modern JS uses .includes() instead
if (arr.includes("banana")) {
console.log("Found"); // โ
Clearer and better
}2. Fast Floor for Positive Numbers:
console.log(~~3.9); // 3 (double NOT truncates decimals)
console.log(~~5.1); // 5
console.log(~~-3.9); // -3
// How it works:
// ~3.9 = ~3 = -4
// ~(-4) = 3
// Warning: Only works within 32-bit integer range
// Better to use Math.floor() for clarityLeft Shift (<<)
The left shift operator shifts bits to the left by specified positions. Empty positions are filled with 0.
How It Works
5 << 1:
Before: 0000 0101 (5)
After: 0000 1010 (10)
โFormula: n << x = n * 2^x
Syntax and Examples
console.log(5 << 1); // 10 (5 * 2ยน = 10)
console.log(5 << 2); // 20 (5 * 2ยฒ = 20)
console.log(5 << 3); // 40 (5 * 2ยณ = 40)
console.log(1 << 4); // 16 (1 * 2โด = 16)
console.log(3 << 5); // 96 (3 * 2โต = 96)
// Binary visualization
console.log((0b101 << 2).toString(2)); // "10100" = 20Practical Use Cases
1. Fast Multiplication by Power of 2:
// Instead of: num * 2
console.log(5 * 2); // 10
console.log(5 << 1); // 10 (faster)
// Instead of: num * 4
console.log(5 * 4); // 20
console.log(5 << 2); // 20 (faster)
// Instead of: num * 8
console.log(5 * 8); // 40
console.log(5 << 3); // 40 (faster)2. Create Bit Masks:
// Create mask for bit position
function createMask(position) {
return 1 << position;
}
console.log(createMask(0).toString(2)); // "1" (0b0001)
console.log(createMask(1).toString(2)); // "10" (0b0010)
console.log(createMask(2).toString(2)); // "100" (0b0100)
console.log(createMask(3).toString(2)); // "1000" (0b1000)3. Power of 2 Calculator:
function powerOf2(exponent) {
return 1 << exponent;
}
console.log(powerOf2(0)); // 1 (2โฐ)
console.log(powerOf2(1)); // 2 (2ยน)
console.log(powerOf2(2)); // 4 (2ยฒ)
console.log(powerOf2(10)); // 1024 (2ยนโฐ)4. RGB to Hex Color:
function rgbToHex(r, g, b) {
return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}
console.log(rgbToHex(255, 87, 51)); // "#ff5733"
console.log(rgbToHex(0, 128, 255)); // "#0080ff"Sign-Propagating Right Shift (>>)
The sign-propagating right shift shifts bits to the right. The leftmost bit (sign bit) is copied to fill empty positions.
How It Works
5 >> 1:
Before: 0000 0101 (5)
After: 0000 0010 (2)
โ
-5 >> 1:
Before: 1111 1011 (-5)
After: 1111 1101 (-3)
โ โ
(Sign bit preserved)Formula: n >> x = Math.floor(n / 2^x)
Syntax and Examples
// Positive numbers
console.log(5 >> 1); // 2 (5 / 2ยน = 2.5 โ 2)
console.log(10 >> 1); // 5 (10 / 2ยน = 5)
console.log(20 >> 2); // 5 (20 / 2ยฒ = 5)
console.log(16 >> 3); // 2 (16 / 2ยณ = 2)
// Negative numbers (sign preserved)
console.log(-5 >> 1); // -3 (keeps negative)
console.log(-10 >> 1); // -5 (keeps negative)
console.log(-20 >> 2); // -5 (keeps negative)
// Binary visualization
console.log((0b1010 >> 1).toString(2)); // "101" = 5Practical Use Cases
1. Fast Integer Division by Power of 2:
// Instead of: Math.floor(num / 2)
console.log(Math.floor(10 / 2)); // 5
console.log(10 >> 1); // 5 (faster)
// Instead of: Math.floor(num / 4)
console.log(Math.floor(20 / 4)); // 5
console.log(20 >> 2); // 5 (faster)
// Works with negative numbers too
console.log(Math.floor(-10 / 2)); // -5
console.log(-10 >> 1); // -52. Extract High Bits:
// Extract upper byte from 16-bit number
let value = 0b1111000011110000;
let upperByte = value >> 8; // 0b11110000 = 240
console.log(upperByte.toString(2).padStart(8, '0')); // "11110000"3. RGB Color Extraction:
function extractRGB(color) {
return {
red: (color >> 16) & 0xFF,
green: (color >> 8) & 0xFF,
blue: color & 0xFF
};
}
let color = 0xFF5733;
console.log(extractRGB(color)); // { red: 255, green: 87, blue: 51 }Zero-Fill Right Shift (>>>)
The zero-fill right shift shifts bits to the right. Empty positions are always filled with 0, regardless of sign.
How It Works
5 >>> 1:
Before: 0000 0101 (5)
After: 0000 0010 (2)
0 โ
-5 >>> 1:
Before: 1111 1011 (-5)
After: 0111 1101 (2,147,483,645)
0 โ
(0 filled, not sign bit!)Syntax and Examples
// Positive numbers (same as >>)
console.log(5 >>> 1); // 2
console.log(10 >>> 1); // 5
console.log(20 >>> 2); // 5
// Negative numbers (different from >>)
console.log(-5 >> 1); // -3 (sign-propagating)
console.log(-5 >>> 1); // 2147483645 (zero-fill, becomes huge positive!)
console.log(-1 >>> 0); // 4294967295 (all bits set to 1)Practical Use Cases
1. Convert to Unsigned 32-bit Integer:
function toUint32(num) {
return num >>> 0;
}
console.log(toUint32(-1)); // 4294967295
console.log(toUint32(-5)); // 4294967291
console.log(toUint32(5)); // 5 (unchanged for positive)
// Useful for bit manipulations requiring unsigned values2. Get Positive Hash Code:
function hashCode(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash >>> 0; // Convert to unsigned 32-bit integer
}
return hash;
}
console.log(hashCode("hello")); // 99162322
console.log(hashCode("world")); // 113318802Bitwise Assignment Operators
Like other operators, bitwise operators have assignment variants:
let x = 5;
// Bitwise AND assignment
x &= 3; // x = x & 3
console.log(x); // 1
// Bitwise OR assignment
x = 5;
x |= 3; // x = x | 3
console.log(x); // 7
// Bitwise XOR assignment
x = 5;
x ^= 3; // x = x ^ 3
console.log(x); // 6
// Left shift assignment
x = 5;
x <<= 2; // x = x << 2
console.log(x); // 20
// Right shift assignment
x = 20;
x >>= 2; // x = x >> 2
console.log(x); // 5
// Zero-fill right shift assignment
x = -5;
x >>>= 1; // x = x >>> 1
console.log(x); // 2147483645Real-World Applications
1. Permission System
// Define permissions as bit flags
const PERMISSIONS = {
NONE: 0b0000, // 0
READ: 0b0001, // 1
WRITE: 0b0010, // 2
DELETE: 0b0100, // 4
ADMIN: 0b1000 // 8
};
class User {
constructor(name) {
this.name = name;
this.permissions = PERMISSIONS.NONE;
}
// Grant permission
grant(permission) {
this.permissions |= permission;
}
// Revoke permission
revoke(permission) {
this.permissions &= ~permission;
}
// Check permission
has(permission) {
return (this.permissions & permission) === permission;
}
// Toggle permission
toggle(permission) {
this.permissions ^= permission;
}
}
let user = new User("John");
// Grant permissions
user.grant(PERMISSIONS.READ);
user.grant(PERMISSIONS.WRITE);
console.log(user.has(PERMISSIONS.READ)); // true
console.log(user.has(PERMISSIONS.WRITE)); // true
console.log(user.has(PERMISSIONS.DELETE)); // false
// Revoke permission
user.revoke(PERMISSIONS.WRITE);
console.log(user.has(PERMISSIONS.WRITE)); // false
// Grant multiple at once
user.grant(PERMISSIONS.READ | PERMISSIONS.DELETE | PERMISSIONS.ADMIN);
console.log(user.has(PERMISSIONS.ADMIN)); // true2. Feature Flags
const FEATURES = {
DARK_MODE: 1 << 0, // 1
NOTIFICATIONS: 1 << 1, // 2
ANALYTICS: 1 << 2, // 4
EXPERIMENTAL: 1 << 3, // 8
BETA_FEATURES: 1 << 4, // 16
ADVANCED_SEARCH: 1 << 5 // 32
};
class FeatureManager {
constructor() {
this.flags = 0;
}
enable(feature) {
this.flags |= feature;
}
disable(feature) {
this.flags &= ~feature;
}
toggle(feature) {
this.flags ^= feature;
}
isEnabled(feature) {
return (this.flags & feature) !== 0;
}
enableMultiple(...features) {
features.forEach(f => this.enable(f));
}
getEnabledFeatures() {
let enabled = [];
for (let [name, flag] of Object.entries(FEATURES)) {
if (this.isEnabled(flag)) {
enabled.push(name);
}
}
return enabled;
}
}
let features = new FeatureManager();
// Enable features
features.enable(FEATURES.DARK_MODE);
features.enable(FEATURES.NOTIFICATIONS);
console.log(features.isEnabled(FEATURES.DARK_MODE)); // true
console.log(features.isEnabled(FEATURES.ANALYTICS)); // false
// Toggle
features.toggle(FEATURES.DARK_MODE);
console.log(features.isEnabled(FEATURES.DARK_MODE)); // false
// Enable multiple
features.enableMultiple(
FEATURES.DARK_MODE,
FEATURES.ANALYTICS,
FEATURES.BETA_FEATURES
);
console.log(features.getEnabledFeatures());
// ["DARK_MODE", "NOTIFICATIONS", "ANALYTICS", "BETA_FEATURES"]3. IP Address Manipulation
class IPAddress {
constructor(ip) {
if (typeof ip === 'string') {
// Convert string to number
let parts = ip.split('.').map(Number);
this.value = (parts[0] << 24) | (parts[1] << 16) |
(parts[2] << 8) | parts[3];
} else {
this.value = ip >>> 0; // Ensure unsigned 32-bit
}
}
toString() {
return [
(this.value >>> 24) & 0xFF,
(this.value >>> 16) & 0xFF,
(this.value >>> 8) & 0xFF,
this.value & 0xFF
].join('.');
}
isInSubnet(subnet, mask) {
let subnetIP = new IPAddress(subnet);
let maskBits = (0xFFFFFFFF << (32 - mask)) >>> 0;
return (this.value & maskBits) === (subnetIP.value & maskBits);
}
}
let ip1 = new IPAddress("192.168.1.100");
let ip2 = new IPAddress("192.168.1.200");
let ip3 = new IPAddress("192.168.2.100");
console.log(ip1.toString()); // "192.168.1.100"
// Check if in same /24 subnet (255.255.255.0)
console.log(ip1.isInSubnet("192.168.1.0", 24)); // true
console.log(ip3.isInSubnet("192.168.1.0", 24)); // false4. Color Manipulation
class Color {
constructor(r, g, b) {
this.value = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
get red() {
return (this.value >> 16) & 0xFF;
}
get green() {
return (this.value >> 8) & 0xFF;
}
get blue() {
return this.value & 0xFF;
}
toHex() {
return '#' + this.value.toString(16).padStart(6, '0');
}
lighten(amount) {
let r = Math.min(255, this.red + amount);
let g = Math.min(255, this.green + amount);
let b = Math.min(255, this.blue + amount);
return new Color(r, g, b);
}
darken(amount) {
let r = Math.max(0, this.red - amount);
let g = Math.max(0, this.green - amount);
let b = Math.max(0, this.blue - amount);
return new Color(r, g, b);
}
invert() {
return new Color(255 - this.red, 255 - this.green, 255 - this.blue);
}
static fromHex(hex) {
let value = parseInt(hex.replace('#', ''), 16);
return new Color(
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF
);
}
}
let color = new Color(255, 87, 51); // Orange
console.log(color.toHex()); // "#ff5733"
let lighter = color.lighten(50);
console.log(lighter.toHex()); // "#ff8959"
let darker = color.darken(50);
console.log(darker.toHex()); // "#cb2301"
let inverted = color.invert();
console.log(inverted.toHex()); // "#00a8cc"
let fromHex = Color.fromHex("#ff5733");
console.log(fromHex.red, fromHex.green, fromHex.blue); // 255 87 51Performance Considerations
When Bitwise Operations are Faster
// Bitwise operations are faster for:
// 1. Integer division/multiplication by powers of 2
console.time('Division');
for (let i = 0; i < 1000000; i++) {
let result = Math.floor(i / 2);
}
console.timeEnd('Division');
console.time('Right Shift');
for (let i = 0; i < 1000000; i++) {
let result = i >> 1;
}
console.timeEnd('Right Shift'); // Typically faster
// 2. Checking even/odd
console.time('Modulo');
for (let i = 0; i < 1000000; i++) {
let isEven = i % 2 === 0;
}
console.timeEnd('Modulo');
console.time('Bitwise AND');
for (let i = 0; i < 1000000; i++) {
let isEven = (i & 1) === 0;
}
console.timeEnd('Bitwise AND'); // Typically faster
// 3. Rounding to integer
console.time('Math.floor');
for (let i = 0; i < 1000000; i++) {
let result = Math.floor(i * 0.5);
}
console.timeEnd('Math.floor');
console.time('Bitwise OR');
for (let i = 0; i < 1000000; i++) {
let result = (i * 0.5) | 0;
}
console.timeEnd('Bitwise OR'); // Typically fasterWhen to Use Bitwise Operations
โ Use when:
- Working with flags/permissions
- Manipulating colors (RGB)
- Network programming (IP addresses, ports)
- Performance-critical loops with simple integer math
- Low-level data manipulation
โ Avoid when:
- Code clarity is more important than micro-optimizations
- Working with large numbers (>32-bit integers)
- Team members unfamiliar with bitwise operations
- Premature optimization (profile first!)
Common Mistakes and Pitfalls
Mistake 1: 32-bit Integer Overflow
// โ Problem - limited to 32-bit
let large = 5000000000; // 5 billion
console.log(large << 1); // -1589934592 (overflow!)
// โ
Use regular multiplication for large numbers
console.log(large * 2); // 10000000000 (correct)Mistake 2: Using Bitwise with Decimals
// โ Decimals are truncated
console.log(5.7 & 3.2); // 1 (5 & 3)
console.log(10.9 | 4.1); // 14 (10 | 4)
// Be aware of truncation
console.log(3.9 | 0); // 3 (truncates, doesn't round)Mistake 3: Sign Bit Confusion
// โ Forgetting about sign bit
let value = 0x80000000; // Largest positive in 31 bits
console.log(value); // -2147483648 (negative due to sign bit!)
// โ
Use >>> for unsigned
console.log(value >>> 0); // 2147483648 (unsigned)Mistake 4: Incorrect Operator Precedence
// โ Wrong precedence
console.log(5 & 3 === 1); // false (5 & (3 === 1))
// โ
Use parentheses
console.log((5 & 3) === 1); // trueMistake 5: Using Bitwise for Boolean Logic
// โ Confusing bitwise with logical
let a = true;
let b = false;
console.log(a & b); // 0 (not false!)
console.log(a | b); // 1 (not true!)
// โ
Use logical operators for booleans
console.log(a && b); // false
console.log(a || b); // trueBest Practices
1. Use Named Constants for Flags
// โ Hard to understand
let user = 7; // What does 7 mean?
// โ
Clear and self-documenting
const READ = 1;
const WRITE = 2;
const DELETE = 4;
let user = READ | WRITE | DELETE; // 7, but clear intent2. Comment Bitwise Code
// โ No explanation
let result = (value & 0xFF00) >> 8;
// โ
Explain what's happening
// Extract upper byte (bits 8-15) from 16-bit value
let upperByte = (value & 0xFF00) >> 8;3. Prefer Clarity Over Micro-Optimization
// โ Clever but unclear
let isEven = !(n & 1);
// โ
Clear and readable (compiler optimizes anyway)
let isEven = n % 2 === 0;
// Only use bitwise if:
// 1. Profiling shows it's a bottleneck
// 2. Team understands bitwise operations
// 3. You're working with actual bits (flags, colors, etc.)4. Use Unsigned Right Shift for Unsigned Values
// โ Can produce negative results
let hash = (hash << 5) - hash;
// โ
Ensure unsigned with >>>
let hash = ((hash << 5) - hash) >>> 0;5. Create Helper Functions
// โ Repeating bitwise logic
let r = (color >> 16) & 0xFF;
let g = (color >> 8) & 0xFF;
let b = color & 0xFF;
// โ
Encapsulate in functions
function getRed(color) { return (color >> 16) & 0xFF; }
function getGreen(color) { return (color >> 8) & 0xFF; }
function getBlue(color) { return color & 0xFF; }
let r = getRed(color);
let g = getGreen(color);
let b = getBlue(color);Summary
Congratulations! ๐ You now have a comprehensive understanding of bitwise operators in JavaScript.
Key Takeaways
โ
Bitwise AND (&)
- Both bits must be 1
- Check bits, masks, permissions
โ
Bitwise OR (|)
- At least one bit must be 1
- Set bits, combine flags
โ
Bitwise XOR (^)
- Bits must be different
- Toggle bits, encryption, swapping
โ
Bitwise NOT (~)
- Inverts all bits
- Formula: ~n = -(n + 1)
โ
Left Shift (<<)
- Shifts left, fills with 0
- Fast multiply by power of 2
โ
Right Shift (>>)
- Shifts right, preserves sign
- Fast divide by power of 2
โ
Zero-fill Right Shift (>>>)
- Shifts right, fills with 0
- Convert to unsigned
โ Common Uses:
- Permission/flag systems
- Color manipulation
- Performance optimization
- Low-level data handling
โ Best Practices:
- Use named constants
- Comment bitwise code
- Prefer clarity over micro-optimization
- Be aware of 32-bit limits
What's Next?
Excellent work! You now understand bitwise operations at a deep level.
In the next lesson, we'll explore Special Operators including:
- Ternary operator (
?:) - typeof operator
- instanceof operator
- void operator
- delete operator
- in operator
- comma operator (
,) - Optional chaining (
?.) - Spread operator (
...)