Состояние в битовых картах
В повседневном коде с недесятичными форматами чисел сталкиваться приходится редко. Но есть кейс, когда двоичный формат приходится в тему — это битовые карты. То есть можно решить, что определённое число — это не просто число, а обозначение какого-то состояния. Например, у нас есть три слота, они могут быть заполнены или нет. Тогда битовые карты будут выглядеть так:
// все три слота свободны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 `})}