В повседневном коде с недесятичными форматами чисел сталкиваться приходится редко. Но есть кейс, когда двоичный формат приходится в тему — это битовые карты. То есть можно решить, что определённое число — это не просто число, а обозначение какого-то состояния. Например, у нас есть три слота, они могут быть заполнены или нет. Тогда битовые карты будут выглядеть так:
// все три слота свободны
000
// первый слот занят, остальные свободны
100
// первый и третий слоты заняты, второй свободен
101
Ок, идём дальше. То, что состояния теперь заданы в виде двоичных чисел, даёт возможность применять к ним двоичные операции! Например, это могут быть двоичные И и ИЛИ. Обозначаются они операторами & и |. Двоичные числа сравниваются побитово (первый бит одного числа с первым битом другого, второй бит одного числа со вторым битом другого…).
В случае логического И оба бита должны быть равными 1, чтобы в результате получить 1, в остальных случаях будет 0:
0 & 0 === 0
0 & 1 === 0
1 & 0 === 0
1 & 1 === 1
В случае логического ИЛИ любой из битов может быть равен 1, чтобы получить в результате 1:
0 | 0 === 0
0 | 1 === 1
1 | 0 === 1
1 | 1 === 1
И теперь, если вернуться к примеру со слотами, мы можем «складывать» два состояния с помощью побитового ИЛИ: 100 | 001 === 101 (первый занятый слот ИЛИ третий занятый слот — это занятые 1 и 3 слот). А из «суммы» двух состояний можно убрать все состояния, кроме определённого, с помощью побитового И: 101 & 100 === 100 (первый с третьим занятые слоты И первый занятый слот — это первый занятый слот).
Теперь к юзкейсу. Вся мякотка маппинга состояний на битовые карты раскрывается в кейсе, когда побитовое ИЛИ используется для создания композиции нескольких отдельных состояний. Представьте, что у нас есть некие состояния:
const MOBILE = 0b0001;
const TABLET = 0b0010;
const LAPTOP = 0b0100;
const DESKTOP = 0b1000;
Можно использовать как просто отдельное состояние, например, MOBILE, так и композицию нескольких состояний, к примеру, MOBILE | LAPTOP (что буквально так и считывается мобайл ИЛИ лаптоп, спасибо TS👋).
Это можно использовать для задания динамических ключей объектов:
const objectMap = {
[MOBILE]: "mobile stuff",
[LAPTOP]: "laptop stuff",
[MOBILE | LAPTOP]: "mobile or laptop stuff",
};
Как вы уже, возможно, догадались, распаковать этот ключ можно с помощью логического И (&), обратной операции. Предположим, что нужно отфильтровать только те ключи, в которых есть MOBILE, то есть нужно ко всем ключам применить & MOBILE:
MOBILE & MOBILE === MOBILE
// у 0b0001 и 0b0001 есть общая 1, получаем 0b0001
LAPTOP & MOBILE === 0
// у 0b0100 и 0b0001 нет общей 1, получаем 0b0000
(MOBILE | LAPTOP) & MOBILE === MOBILE
// у 0b0101 и 0b0001 есть общая 1, получаем 0b0001
Дальше отфильтровываем те значения, ключи которых выдали нули и готово!
Вот как выглядит функция-фильтровщик:
function getFilteredValues(objectMap, filterKey) {
return Object.entries(objectMap)
.map(([key, value]) => {
const shouldInclude =
(key & MOBILE) === filterKey ||
(key & TABLET) === filterKey ||
(key & LAPTOP) === filterKey ||
(key & DESKTOP) === filterKey;
return shouldInclude ? value : null;
})
.filter(Boolean);
}
Код https://codepen.io/juwain/pen/KKEGRrB?editors=0011
Кстати, небольшой оффтоп: обратите внимание на ссылку. Codepen тоже использует битовую карту, чтобы обозначить, какие редакторы по умолчанию скрыть (0), а какие показать (1) 👾
Я использовал такой подход для динамического создания стилей для каждого типа девайса. Иногда стили для соседних девайсов повторялись, не хотелось их дублировать и получилось сгруппировать по типу MOBILE | LAPTOP.
Пример (это Styled Components):
${setResponsiveStyles({
[MOBILE]: css`
// some only mobile styles
`,
[MOBILE | TABLET]: css`
// some mobile or tablet styles
`,
[LAPTOP]: css`
// some only laptop styles
`,
[DESKTOP]: css`
// some only desktop styles
`
})}