Представь: ты проектируешь систему прав доступа для CMS. У пользователя может быть любая комбинация прав: читать, писать, удалять, публиковать, администрировать. Хранить каждое право отдельным boolean — 5 полей в базе данных. Или хранить всё в одном числе через битовые флаги. Именно так устроена система прав Unix, права файлов S3 и многие другие системы.
Побитовые операторы позволяют эффективно работать с наборами флагов: проверять, устанавливать, снимать и переключать отдельные биты одной операцией. Это быстро, компактно и работает напрямую с аппаратным уровнем.
32-битное целое число хранится как последовательность нулей и единиц:
// Число 5 в двоичном виде (32 бита):
// 00000000 00000000 00000000 00000101
// ^^^
// 5 = 4 + 1 = 2² + 2⁰
console.log((5).toString(2)) // '101'
console.log((13).toString(2)) // '1101' (8+4+1)
console.log((255).toString(2)) // '11111111'// 0101 (5)
// & 0011 (3)
// = 0001 (1)
console.log(5 & 3) // 1
console.log(12 & 10) // 8 (1100 & 1010 = 1000)// 0101 (5)
// | 0011 (3)
// = 0111 (7)
console.log(5 | 3) // 7
console.log(4 | 2) // 6 (100 | 010 = 110)// 0101 (5)
// ^ 0011 (3)
// = 0110 (6)
console.log(5 ^ 3) // 6
console.log(5 ^ 5) // 0 — XOR с самим собой всегда 0// ~n = -(n + 1) для 32-битных целых
console.log(~5) // -6
console.log(~0) // -1
console.log(~-1) // 0// Сдвиг на N позиций = умножение на 2^N
console.log(1 << 0) // 1 (00001)
console.log(1 << 1) // 2 (00010)
console.log(1 << 2) // 4 (00100)
console.log(1 << 3) // 8 (01000)
console.log(5 << 1) // 10 (0101 → 1010)// Сдвиг на N позиций = деление на 2^N (целочисленное)
console.log(8 >> 1) // 4
console.log(8 >> 2) // 2
console.log(-8 >> 1) // -4 (знак сохраняется)console.log(8 >>> 1) // 4
console.log(-8 >>> 1) // 2147483644 (знаковый бит становится обычным битом)Битовые маски — эффективный способ хранить набор булевых флагов в одном числе:
// Определяем флаги как степени двойки (каждый занимает свой бит)
const READ = 1 // 001
const WRITE = 2 // 010
const EXEC = 4 // 100
const ADMIN = 8 // 1000
// Создаём маску прав (комбинируем через |)
let permissions = READ | WRITE // 011 = 3 (чтение и запись)
// Проверка флага (& с флагом → ненулевое значение если флаг установлен)
console.log((permissions & READ) !== 0) // true — есть право на чтение
console.log((permissions & EXEC) !== 0) // false — нет права на исполнение
// Установка флага (|)
permissions = permissions | EXEC // добавить EXEC → 111 = 7
// Снятие флага (& ~flag)
permissions = permissions & ~WRITE // убрать WRITE → 101 = 5
// Переключение флага (^)
permissions = permissions ^ ADMIN // добавить ADMIN → 1101 = 13
permissions = permissions ^ ADMIN // убрать ADMIN → 0101 = 5// Math.floor через | 0 (только для чисел в диапазоне 32-bit int)
console.log(3.7 | 0) // 3
console.log(-3.7 | 0) // -3 (усечение, не floor!)
console.log(3.7 | 0) // 3
// Аналог: Math.trunc(3.7)
// Проверка чётности (последний бит: 0 = чётное, 1 = нечётное)
const isEven = n => (n & 1) === 0
const isOdd = n => (n & 1) !== 0
console.log(isEven(42)) // true
console.log(isOdd(7)) // true
// Быстрое умножение/деление на степень двойки
console.log(3 << 3) // 24 = 3 * 8 = 3 * 2³
console.log(64 >> 2) // 16 = 64 / 4 = 64 / 2²
// Проверка степени двойки
const isPowerOf2 = n => n > 0 && (n & (n - 1)) === 0
console.log(isPowerOf2(16)) // true
console.log(isPowerOf2(12)) // falseВ Unix каждый файл имеет три группы прав (owner, group, other) по три бита (rwx):
// chmod 755 → owner: rwx (7), group: r-x (5), other: r-x (5)
// 7 = 111₂, 5 = 101₂
const OWNER_R = 0o400 // 100 000 000
const OWNER_W = 0o200 // 010 000 000
const OWNER_X = 0o100 // 001 000 000
const GROUP_R = 0o040 // 000 100 000
const GROUP_X = 0o010 // 000 001 000
const OTHER_R = 0o004 // 000 000 100
const OTHER_X = 0o001 // 000 000 001
const mode755 = OWNER_R | OWNER_W | OWNER_X | GROUP_R | GROUP_X | OTHER_R | OTHER_X
console.log(mode755.toString(8)) // 755
// Проверяем права
console.log((mode755 & OWNER_W) !== 0) // true — владелец может писать
console.log((mode755 & GROUP_R) !== 0) // true — группа может читать1. Использовать | 0 для floor отрицательных чисел
// ПЛОХО — | 0 это Math.trunc, а не Math.floor!
console.log(-3.7 | 0) // -3, а не -4!
console.log(Math.floor(-3.7)) // -4 (правильно)
// ХОРОШО — используй Math.floor или Math.trunc явно
const truncated = Math.trunc(3.7) // 3
const floored = Math.floor(3.7) // 3
// Для отрицательных разница критична2. Применять побитовые операции к числам больше 32-bit
// ПЛОХО — число обрезается до 32 бит!
const bigNum = 2 ** 33 // 8589934592 — больше 32 бит
console.log(bigNum | 0) // 0 — потеряли всё!
// ХОРОШО — проверяй диапазон, используй только для маленьких целых
// Безопасный диапазон: -2147483648 до 2147483647 (2^31 - 1)
const safeMax = 2 ** 31 - 1 // 2147483647
console.log(safeMax | 0) // 2147483647 — безопасно3. Забыть добавить скобки — приоритет & ниже, чем ===
// ПЛОХО — & имеет низкий приоритет, сначала выполнится сравнение
if (permissions & READ === 0) { ... } // читается как: permissions & (READ === 0) → неправильно!
// ХОРОШО — скобки обязательны
if ((permissions & READ) !== 0) { ... } // сначала &, потом сравнениеchmod 755 — это битовая маска, rwxr-xr-x закодировано в 9 битахСистема прав доступа на битовых флагах: проверка, установка, снятие и переключение прав
// Система прав доступа на битовых флагах
// Каждое право — отдельный бит (степень двойки)
const Permissions = {
NONE: 0, // 0000 0000
READ: 1, // 0000 0001
WRITE: 2, // 0000 0010
DELETE: 4, // 0000 0100
SHARE: 8, // 0000 1000
ADMIN: 16, // 0001 0000
}
// Вспомогательные функции
function hasPermission(mask, flag) {
return (mask & flag) !== 0
}
function addPermission(mask, flag) {
return mask | flag
}
function removePermission(mask, flag) {
return mask & ~flag
}
function togglePermission(mask, flag) {
return mask ^ flag
}
// Читаемое представление маски прав
function describePermissions(mask) {
const names = Object.entries(Permissions)
.filter(([name, flag]) => flag !== 0 && hasPermission(mask, flag))
.map(([name]) => name)
return names.length > 0 ? names.join(' | ') : 'NONE'
}
// ===== Демонстрация =====
console.log('=== Система прав доступа ===')
// Пользователь с базовыми правами
let userPerms = Permissions.READ | Permissions.WRITE
console.log('Изначальные права:', describePermissions(userPerms))
// READ | WRITE
console.log('Может читать?', hasPermission(userPerms, Permissions.READ)) // true
console.log('Может удалять?', hasPermission(userPerms, Permissions.DELETE)) // false
// Добавить право на удаление
userPerms = addPermission(userPerms, Permissions.DELETE)
console.log('\nПосле добавления DELETE:', describePermissions(userPerms))
// READ | WRITE | DELETE
// Снять право на запись
userPerms = removePermission(userPerms, Permissions.WRITE)
console.log('После удаления WRITE:', describePermissions(userPerms))
// READ | DELETE
// Переключить SHARE (вкл/выкл)
userPerms = togglePermission(userPerms, Permissions.SHARE)
console.log('После включения SHARE:', describePermissions(userPerms))
// READ | DELETE | SHARE
userPerms = togglePermission(userPerms, Permissions.SHARE)
console.log('После выключения SHARE:', describePermissions(userPerms))
// READ | DELETE
// ===== Роли пользователей =====
console.log('\n=== Роли с предустановленными правами ===')
const Roles = {
GUEST: Permissions.READ,
USER: Permissions.READ | Permissions.WRITE,
EDITOR: Permissions.READ | Permissions.WRITE | Permissions.DELETE | Permissions.SHARE,
ADMIN: Permissions.READ | Permissions.WRITE | Permissions.DELETE | Permissions.SHARE | Permissions.ADMIN,
}
const users = [
{ name: 'Гость', role: Roles.GUEST },
{ name: 'Иван', role: Roles.USER },
{ name: 'Редактор', role: Roles.EDITOR },
{ name: 'Дмитрий', role: Roles.ADMIN },
]
const actions = [
{ name: 'читать', flag: Permissions.READ },
{ name: 'писать', flag: Permissions.WRITE },
{ name: 'удалять', flag: Permissions.DELETE },
{ name: 'делиться', flag: Permissions.SHARE },
{ name: 'управлять', flag: Permissions.ADMIN },
]
for (const user of users) {
const allowed = actions
.filter(a => hasPermission(user.role, a.flag))
.map(a => a.name)
console.log(`${user.name.padEnd(12)}: ${allowed.join(', ')}`)
}
// ===== Трюки =====
console.log('\n=== Побитовые трюки ===')
// Проверка чётности
const numbers = [0, 1, 2, 7, 12, 15, 100, 101]
for (const n of numbers) {
console.log(`${n}: ${(n & 1) === 0 ? 'чётное' : 'нечётное'}`)
}
// Быстрое Math.trunc через | 0
console.log('\n3.9 | 0 =', 3.9 | 0) // 3
console.log('-3.9 | 0 =', -3.9 | 0) // -3
// Проверка степени двойки
const checkPow2 = n => n > 0 && (n & (n - 1)) === 0
const testNums = [1, 2, 3, 4, 8, 12, 16, 31, 32, 64]
console.log('\nСтепени двойки:')
for (const n of testNums) {
if (checkPow2(n)) console.log(` ${n} = 2^${Math.log2(n)}`)
}Представь: ты проектируешь систему прав доступа для CMS. У пользователя может быть любая комбинация прав: читать, писать, удалять, публиковать, администрировать. Хранить каждое право отдельным boolean — 5 полей в базе данных. Или хранить всё в одном числе через битовые флаги. Именно так устроена система прав Unix, права файлов S3 и многие другие системы.
Побитовые операторы позволяют эффективно работать с наборами флагов: проверять, устанавливать, снимать и переключать отдельные биты одной операцией. Это быстро, компактно и работает напрямую с аппаратным уровнем.
32-битное целое число хранится как последовательность нулей и единиц:
// Число 5 в двоичном виде (32 бита):
// 00000000 00000000 00000000 00000101
// ^^^
// 5 = 4 + 1 = 2² + 2⁰
console.log((5).toString(2)) // '101'
console.log((13).toString(2)) // '1101' (8+4+1)
console.log((255).toString(2)) // '11111111'// 0101 (5)
// & 0011 (3)
// = 0001 (1)
console.log(5 & 3) // 1
console.log(12 & 10) // 8 (1100 & 1010 = 1000)// 0101 (5)
// | 0011 (3)
// = 0111 (7)
console.log(5 | 3) // 7
console.log(4 | 2) // 6 (100 | 010 = 110)// 0101 (5)
// ^ 0011 (3)
// = 0110 (6)
console.log(5 ^ 3) // 6
console.log(5 ^ 5) // 0 — XOR с самим собой всегда 0// ~n = -(n + 1) для 32-битных целых
console.log(~5) // -6
console.log(~0) // -1
console.log(~-1) // 0// Сдвиг на N позиций = умножение на 2^N
console.log(1 << 0) // 1 (00001)
console.log(1 << 1) // 2 (00010)
console.log(1 << 2) // 4 (00100)
console.log(1 << 3) // 8 (01000)
console.log(5 << 1) // 10 (0101 → 1010)// Сдвиг на N позиций = деление на 2^N (целочисленное)
console.log(8 >> 1) // 4
console.log(8 >> 2) // 2
console.log(-8 >> 1) // -4 (знак сохраняется)console.log(8 >>> 1) // 4
console.log(-8 >>> 1) // 2147483644 (знаковый бит становится обычным битом)Битовые маски — эффективный способ хранить набор булевых флагов в одном числе:
// Определяем флаги как степени двойки (каждый занимает свой бит)
const READ = 1 // 001
const WRITE = 2 // 010
const EXEC = 4 // 100
const ADMIN = 8 // 1000
// Создаём маску прав (комбинируем через |)
let permissions = READ | WRITE // 011 = 3 (чтение и запись)
// Проверка флага (& с флагом → ненулевое значение если флаг установлен)
console.log((permissions & READ) !== 0) // true — есть право на чтение
console.log((permissions & EXEC) !== 0) // false — нет права на исполнение
// Установка флага (|)
permissions = permissions | EXEC // добавить EXEC → 111 = 7
// Снятие флага (& ~flag)
permissions = permissions & ~WRITE // убрать WRITE → 101 = 5
// Переключение флага (^)
permissions = permissions ^ ADMIN // добавить ADMIN → 1101 = 13
permissions = permissions ^ ADMIN // убрать ADMIN → 0101 = 5// Math.floor через | 0 (только для чисел в диапазоне 32-bit int)
console.log(3.7 | 0) // 3
console.log(-3.7 | 0) // -3 (усечение, не floor!)
console.log(3.7 | 0) // 3
// Аналог: Math.trunc(3.7)
// Проверка чётности (последний бит: 0 = чётное, 1 = нечётное)
const isEven = n => (n & 1) === 0
const isOdd = n => (n & 1) !== 0
console.log(isEven(42)) // true
console.log(isOdd(7)) // true
// Быстрое умножение/деление на степень двойки
console.log(3 << 3) // 24 = 3 * 8 = 3 * 2³
console.log(64 >> 2) // 16 = 64 / 4 = 64 / 2²
// Проверка степени двойки
const isPowerOf2 = n => n > 0 && (n & (n - 1)) === 0
console.log(isPowerOf2(16)) // true
console.log(isPowerOf2(12)) // falseВ Unix каждый файл имеет три группы прав (owner, group, other) по три бита (rwx):
// chmod 755 → owner: rwx (7), group: r-x (5), other: r-x (5)
// 7 = 111₂, 5 = 101₂
const OWNER_R = 0o400 // 100 000 000
const OWNER_W = 0o200 // 010 000 000
const OWNER_X = 0o100 // 001 000 000
const GROUP_R = 0o040 // 000 100 000
const GROUP_X = 0o010 // 000 001 000
const OTHER_R = 0o004 // 000 000 100
const OTHER_X = 0o001 // 000 000 001
const mode755 = OWNER_R | OWNER_W | OWNER_X | GROUP_R | GROUP_X | OTHER_R | OTHER_X
console.log(mode755.toString(8)) // 755
// Проверяем права
console.log((mode755 & OWNER_W) !== 0) // true — владелец может писать
console.log((mode755 & GROUP_R) !== 0) // true — группа может читать1. Использовать | 0 для floor отрицательных чисел
// ПЛОХО — | 0 это Math.trunc, а не Math.floor!
console.log(-3.7 | 0) // -3, а не -4!
console.log(Math.floor(-3.7)) // -4 (правильно)
// ХОРОШО — используй Math.floor или Math.trunc явно
const truncated = Math.trunc(3.7) // 3
const floored = Math.floor(3.7) // 3
// Для отрицательных разница критична2. Применять побитовые операции к числам больше 32-bit
// ПЛОХО — число обрезается до 32 бит!
const bigNum = 2 ** 33 // 8589934592 — больше 32 бит
console.log(bigNum | 0) // 0 — потеряли всё!
// ХОРОШО — проверяй диапазон, используй только для маленьких целых
// Безопасный диапазон: -2147483648 до 2147483647 (2^31 - 1)
const safeMax = 2 ** 31 - 1 // 2147483647
console.log(safeMax | 0) // 2147483647 — безопасно3. Забыть добавить скобки — приоритет & ниже, чем ===
// ПЛОХО — & имеет низкий приоритет, сначала выполнится сравнение
if (permissions & READ === 0) { ... } // читается как: permissions & (READ === 0) → неправильно!
// ХОРОШО — скобки обязательны
if ((permissions & READ) !== 0) { ... } // сначала &, потом сравнениеchmod 755 — это битовая маска, rwxr-xr-x закодировано в 9 битахСистема прав доступа на битовых флагах: проверка, установка, снятие и переключение прав
// Система прав доступа на битовых флагах
// Каждое право — отдельный бит (степень двойки)
const Permissions = {
NONE: 0, // 0000 0000
READ: 1, // 0000 0001
WRITE: 2, // 0000 0010
DELETE: 4, // 0000 0100
SHARE: 8, // 0000 1000
ADMIN: 16, // 0001 0000
}
// Вспомогательные функции
function hasPermission(mask, flag) {
return (mask & flag) !== 0
}
function addPermission(mask, flag) {
return mask | flag
}
function removePermission(mask, flag) {
return mask & ~flag
}
function togglePermission(mask, flag) {
return mask ^ flag
}
// Читаемое представление маски прав
function describePermissions(mask) {
const names = Object.entries(Permissions)
.filter(([name, flag]) => flag !== 0 && hasPermission(mask, flag))
.map(([name]) => name)
return names.length > 0 ? names.join(' | ') : 'NONE'
}
// ===== Демонстрация =====
console.log('=== Система прав доступа ===')
// Пользователь с базовыми правами
let userPerms = Permissions.READ | Permissions.WRITE
console.log('Изначальные права:', describePermissions(userPerms))
// READ | WRITE
console.log('Может читать?', hasPermission(userPerms, Permissions.READ)) // true
console.log('Может удалять?', hasPermission(userPerms, Permissions.DELETE)) // false
// Добавить право на удаление
userPerms = addPermission(userPerms, Permissions.DELETE)
console.log('\nПосле добавления DELETE:', describePermissions(userPerms))
// READ | WRITE | DELETE
// Снять право на запись
userPerms = removePermission(userPerms, Permissions.WRITE)
console.log('После удаления WRITE:', describePermissions(userPerms))
// READ | DELETE
// Переключить SHARE (вкл/выкл)
userPerms = togglePermission(userPerms, Permissions.SHARE)
console.log('После включения SHARE:', describePermissions(userPerms))
// READ | DELETE | SHARE
userPerms = togglePermission(userPerms, Permissions.SHARE)
console.log('После выключения SHARE:', describePermissions(userPerms))
// READ | DELETE
// ===== Роли пользователей =====
console.log('\n=== Роли с предустановленными правами ===')
const Roles = {
GUEST: Permissions.READ,
USER: Permissions.READ | Permissions.WRITE,
EDITOR: Permissions.READ | Permissions.WRITE | Permissions.DELETE | Permissions.SHARE,
ADMIN: Permissions.READ | Permissions.WRITE | Permissions.DELETE | Permissions.SHARE | Permissions.ADMIN,
}
const users = [
{ name: 'Гость', role: Roles.GUEST },
{ name: 'Иван', role: Roles.USER },
{ name: 'Редактор', role: Roles.EDITOR },
{ name: 'Дмитрий', role: Roles.ADMIN },
]
const actions = [
{ name: 'читать', flag: Permissions.READ },
{ name: 'писать', flag: Permissions.WRITE },
{ name: 'удалять', flag: Permissions.DELETE },
{ name: 'делиться', flag: Permissions.SHARE },
{ name: 'управлять', flag: Permissions.ADMIN },
]
for (const user of users) {
const allowed = actions
.filter(a => hasPermission(user.role, a.flag))
.map(a => a.name)
console.log(`${user.name.padEnd(12)}: ${allowed.join(', ')}`)
}
// ===== Трюки =====
console.log('\n=== Побитовые трюки ===')
// Проверка чётности
const numbers = [0, 1, 2, 7, 12, 15, 100, 101]
for (const n of numbers) {
console.log(`${n}: ${(n & 1) === 0 ? 'чётное' : 'нечётное'}`)
}
// Быстрое Math.trunc через | 0
console.log('\n3.9 | 0 =', 3.9 | 0) // 3
console.log('-3.9 | 0 =', -3.9 | 0) // -3
// Проверка степени двойки
const checkPow2 = n => n > 0 && (n & (n - 1)) === 0
const testNums = [1, 2, 3, 4, 8, 12, 16, 31, 32, 64]
console.log('\nСтепени двойки:')
for (const n of testNums) {
if (checkPow2(n)) console.log(` ${n} = 2^${Math.log2(n)}`)
}Реализуй четыре функции для работы с битовыми флагами: hasPermission(mask, flag) — проверяет наличие флага, addPermission(mask, flag) — добавляет флаг, removePermission(mask, flag) — снимает флаг, togglePermission(mask, flag) — переключает флаг. Затем используй их для создания функции applyRoleDefaults(currentMask, roleMask) которая добавляет все права роли к текущей маске.
hasPermission: (mask & flag) !== 0. addPermission: mask | flag. removePermission: mask & ~flag. togglePermission: mask ^ flag. applyRoleDefaults: currentMask | roleMask