Что такое побитовые операторы?

Недавно я работал над проектом и наткнулся на некоторые логические операторы, с которыми я не был знаком (& и |). Сначала я подумал, что это ошибка, и это должны были быть очевидные операторы && (И) и || (ИЛИ).

Посмотрев в Интернете, я обнаружил, что это не было ошибкой, но это были побитовые операторы. Я не слышал об этом раньше, поэтому потратил некоторое время, чтобы понять, что именно происходит.

Я решил написать это, так как не нашел никаких исчерпывающих статей учебного типа, объясняющих реальный сценарий для побитовых операторов.

В этой статье речь пойдет только о побитовых операторах AND (&) и OR (|). Есть и другие, но это должно помочь вам понять основные концепции, чтобы понять и их.

Мне нужно многое объяснить, и я постараюсь сделать это как можно проще, так что придерживайтесь меня до конца статьи. Если вы испытываете трудности, свяжитесь с нами, и я сделаю все возможное, чтобы помочь вам.

Вот - полный пример кода, если вы хотите его увидеть.

Пример

Как и все мои статьи (по крайней мере, пока), мне нравится объяснять концепцию, используя сценарий реального мира. Сценарий для этой статьи следующий:

В настоящее время вы являетесь разработчиком в компании с четкой организационной структурой и системой управления персоналом. Когда человек входит в эту систему управления персоналом, он должен иметь возможность видеть информацию для всех на том же уровне или ниже.

Мы собираемся сделать class, который после создания экземпляра с названием должности пользователя будет сообщать, есть ли у них доступ к другой информации о названиях должностей.

Давайте начнем

Наша примерная компания небольшая. Настолько маленький, что у него всего четыре позиции, поэтому давайте сначала создадим объект для представления этих позиций.

const positions = {
  developer: 1,
  techLead: 2,
  headOfIt: 4,
  managingDirector: 8
}

У каждой должности есть название должности и числовой индекс. Вы могли заметить, что числа не увеличиваются на единицу, почему? Эта часть чрезвычайно важна для работы побитовых операторов, поэтому давайте уделим ей немного времени.

Как работают побитовые операторы?

Сначала давайте посмотрим на MDN объяснение побитовых операторов:

Поразрядные операторы обрабатывают свои операнды как последовательность из 32 бит (нулей и единиц), а не как десятичные, шестнадцатеричные или восьмеричные numbers.

Если вы владеете / используете Macbook, вы можете легко увидеть это представление в своем приложении «Калькулятор» (View --> Programmer), которое покажет вам что-то вроде этого в верхней части калькулятора:

Это показывает вам представление 64-бит. Вернемся ко второй части объяснения MDN;

Например, десятичное число девять имеет двоичное представление 1001. Побитовые операторы выполняют свои операции с такими двоичными представлениями, но они возвращают стандартные числовые значения JavaScript.

Как число 9 соответствует 1001?

Давайте сначала посмотрим, как устроены двоичные представления. Используя изображение калькулятора выше, мы собираемся взглянуть на первый набор нулей:

Первый ноль (справа → слева) представляет собой число 1. Каждый последующий ноль принимает предыдущее значение и умножается на два, так что этот набор из восьми нулей будет выглядеть следующим образом:

Итак, на вопрос Как число 9 представляет 1001? сначала нужно вычислить, используя приведенные выше числа. 8 + 1 = 9

Таким образом, двоичное представление - 0000 10001, или для краткости 1001.

Вернемся к нашему примеру

Теперь, если мы вернемся к нашему объекту positions, вы должны увидеть, откуда мы взяли наши числовые значения (1, 2, 4 и 8).

Ранее я объяснял, что мы собираемся делать, вот напоминание:

В настоящее время вы являетесь разработчиком в компании с четкой организационной структурой и системой управления персоналом. Когда человек входит в эту систему управления персоналом, он должен иметь возможность видеть информацию для всех на том же уровне или ниже.

Мы собираемся сделать class, который после создания экземпляра с названием должности пользователя будет сообщать, есть ли у него доступ к другой информации о должностях. Итак, вот еще одна таблица с нашими ожиданиями ...

Таким образом, управляющий директор должен видеть всех, разработчики должны видеть только других разработчиков и никого другого.

Давай сделаем код

Сначала давайте создадим class - назовем его Permissions.

class Permissions {
 
}

Теперь давайте добавим конструктор; помните, мы ожидаем, что пользователь отправит текстовую строку со своей позицией (например, «headOfIt»).

// The user will pass in a job title in string format
constructor(position) {
    // Get the numerical value of the passed in poistion from the
    // positions object   
    this.requiredPosition = positions[position];
    
    // Perform a switch statement on the numerical value.
    switch (this.requiredPosition) {
      case positions.managingDirector:
        this.permissions = 
             positions.developer | 
             positions.techLead | 
             positions.headOfIt | 
             positions.managingDirector;
        break;
      case positions.headOfIt:
        this.permissions = 
             positions.developer | 
             positions.techLead | 
             positions.headOfIt;
        break;
      case positions.techLead:
        this.permissions = 
             positions.developer | 
             positions.techLead
        break;
      case positions.developer:
          this.permissions = positions.developer;
          break;
      default:
        break;
    }

Давайте посмотрим, что здесь происходит.

  1. Когда вы создаете экземпляр этого класса, вы передаете текстовую строку (давайте придерживаться нашего примера «headOfIt»).
  2. this.requiredPosition присваивается числовое значение из объекта позиций, который мы создали ранее (4, если headOfIt был передан).
const positions = {
  developer: 1,
  techLead: 2,
  headOfIt: 4, // headOfIt equals 4
  managingDirector: 8
}

3. Выполняем регистр для числового значения (4).

4. Затем мы используем оператор побитового ИЛИ (|), чтобы присвоить двоичное представление this.permissions.

Так как же работает побитовый оператор ИЛИ?

Если вы остались со мной до сих пор, вы, вероятно, задаетесь вопросом, когда мы на самом деле будем говорить о побитовых операторах. Хорошо, теперь ответ.

Давайте продолжим использовать headOfIt в качестве нашего примера и извлечем этот бит из оператора case.

this.permissions = 
             positions.developer | 
             positions.techLead | 
             positions.headOfIt;

Если мы вернемся к нашему positions объекту, мы узнаем, что positions.developer равно 1, positions.techLead равно 2 и positions.headOfIt равно 4. Давайте запишем это как двоичные представления:

Первая строка - 1, вторая 2 и третья 4. Когда мы говорим об операторах побитового ИЛИ (|) и И (&), мы сравниваем двоичные представления по столбцам.

Оператор OR задает вопрос, является ли какое-либо из значений в столбце a 1? Если ответ true, возвращается 1.

Вернемся к нашему визуальному представлению ...

Нижняя строка (0000 0111) - это то, что возвращается и присваивается this.permissions. Все столбцы, у которых нет 1, возвращаются как 0, а те, у которых есть, - как 1.

Итак, наш пример с использованием headOfIt присваивает this.permissions 0000 0111 или, как числовое представление, 7 (4 + 2 + 1).

Завершение урока

Последняя часть нашего класса - это несколько методов.

developer() {
 isAccessGranted(this.permissions, 'developer', positions.developer);
}
  
techLead() {
    isAccessGranted(this.permissions, 'techLead', positions.techLead);
  }
  
headOfIt() {
    isAccessGranted(this.permissions, 'headOfIt', positions.headOfIt);
  }
  
managingDirector() {
    isAccessGranted(this.permissions, 'managingDirector', positions.managingDirector);
  }

Каждый метод вызывает функцию, которую мы определим дальше, передав нашу this.permissions переменную, строку с названием должности и числовое представление этой должности (взято из нашего объекта positions).

Вот полный класс для справки:

class Permissions {
  constructor(position) {
    // Get the numerical value of the passed in poistion
    this.requiredPosition = positions[position];
    
    // Get binary representation depending on what position is passed in
    switch (this.requiredPosition) {
      case positions.managingDirector:
        this.permissions = positions.developer | positions.techLead | positions.headOfIt | 
            positions.managingDirector;
        break;
      case positions.headOfIt:
        this.permissions = positions.developer | positions.techLead | positions.headOfIt;
        break;
      case positions.techLead:
        this.permissions = positions.developer | positions.techLead
        break;
      case positions.developer:
          this.permissions = positions.developer;
          break;
      default:
        break;
    }
    
  }
  
  developer() {
    isAccessGranted(this.permissions, 'developer', positions.developer);
  }
  
  techLead() {
    isAccessGranted(this.permissions, 'techLead', positions.techLead);
  }
  
  headOfIt() {
    isAccessGranted(this.permissions, 'headOfIt', positions.headOfIt);
  }
  
  managingDirector() {
    isAccessGranted(this.permissions, 'managingDirector', positions.managingDirector);
  }
}

Функция isAccessGranted

Эта функция сообщит пользователю, разрешен ли доступ или нет.

Давайте просто создадим экземпляр нашего класса, чтобы мы могли выполнить эту функцию с некоторыми примерами.

const headOfIt = new Permissions('headOfIt');

Теперь давайте вызовем один из методов нашего класса.

headOfIt.developer();

Затем вызывается isAccessGranted

isAccessGranted(this.permissions, 'developer', positions.developer);

В этот момент:

  • this.permissions равно 7 (как объяснялось ранее)
  • positions.developer равно 1 (см. Объект positions)

Теперь давайте сделаем нашу функцию…

const isAccessGranted = (permissions, requiredPositionName, requiredPosition) => {
  
  if((permissions & requiredPosition) !== requiredPosition ) {
    console.log('Access Denied');
    return;
  }
  
  console.log('Access Granted');
}
  • Наша функция вызывается с тремя параметрами: permissions (7), requiredPositionName («разработчик») и requiredPosition (1).
  • Выполняется побитовое И (&); если значение этого параметра равно requiredPosition, тогда мы регистрируем «Доступ разрешен», иначе мы регистрируем «Доступ запрещен».

Побитовый оператор И

Давайте посмотрим на наше заявление IF

if((permissions & requiredPosition) !== requiredPosition ) {}

В отличие от оператора побитового ИЛИ, побитовый оператор И возвращает только 1, если все числа в столбце равны 1.

Так что помните, что permissions равно 7, а requiredPosition равно 1. Давайте посмотрим на двоичные представления:

Первая строка - 7, вторая - 1. Возвращаемое значение - 1, потому что это единственный столбец, у которого 1 в обоих столбцах.

Таким образом, результат побитового оператора - 1, а requiredPosition имеет значение 1, поэтому оператор IF возвращает истину, а «Доступ разрешен» регистрируется.

Но что, если у них не должно быть доступа?

Давай попробуем ... Мы знаем, что руководитель ИТ-отдела не должен иметь доступа к информации управляющих директоров. Итак, давайте назовем этот метод класса

headOfIt.managingDirector();

Затем этот метод вызывает isAccessGranted с другим набором значений.

isAccessGranted(this.permissions, 'managingDirector', positions.managingDirector);
  • this.permissions по-прежнему равно 7
  • positions.managingDirector равно 8.

Итак, вернемся к нашей функции isAccessGranted:

const isAccessGranted = (permissions, requiredPositionName, requiredPosition) => {
  
  if((permissions & requiredPosition) !== requiredPosition ) {
    console.log('Access Denied');
    return;
  }
  
  console.log('Access Granted');
}

Наши параметры следующие:

  • permissions = 7
  • requiredPositionName = «управляющий директор»
  • requiredPosition = 8

А теперь давайте рассмотрим наш оператор IF по очереди:

if((permissions & requiredPosition) !== requiredPosition ) {
    console.log('Access Denied');
    return;
  }
  • permissions & requiredPosition - глядя на визуальное представление ниже, нет столбцов с 1 в обоих; следовательно, результатом этого оператора будет 0000 0000.

  • (permissions & requiredPosition) !== requiredPosition - Совпадает ли результат оператора (0000 0000или 0) с requiredPosition (8)?
  • Это не значит, что сообщение «Доступ запрещен» регистрируется.

Вот и все

Надеюсь, это объяснило, что такое побитовые операторы и как их использовать.

Это была длинная статья, но не стесняйтесь обращаться к нам, если у вас возникнут какие-либо вопросы.