26

Может ли выражение (a == 1 && a == 2 && a == 3) когда-либо оцениться как истинное?

13

Описание проблемы

Я столкнулся с интересным вопросом, который мне задали на собеседовании в одной крупной технологической компании. Вопрос звучал так: может ли выражение <code>(a == 1 && a == 2 && a == 3)</code> когда-либо оцениваться как <code>true</code> в JavaScript?

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

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

5 ответ(ов)

6

Да, это возможно!

var i = 0;

with({
  get a() {
    return ++i;
  }
}) {
  if (a == 1 && a == 2 && a == 3)
    console.log("wohoo");
}

В этом коде используется геттер внутри оператора with, что позволяет значению a оцениваться в три разных значения.

Тем не менее, это не значит, что данный подход следует использовать в реальном коде...

Ещё более удивительно, этот трюк также работает и с использованием ===:

var i = 0;

with({
  get a() {
    return ++i;
  }
}) {
  if (a !== a)
    console.log("yep, this is printed.");
}

В этом случае условие a !== a всегда будет истинным, и сообщение будет напечатано. Однако следует всегда помнить, что такие техники могут создавать запутанный код и усложнять его поддержку, поэтому их лучше избегать в практике программирования!

5

Этот код демонстрирует интересное поведение в JavaScript, где мы можем переопределить методы объекта, чтобы они возвращали разные значения в зависимости от контекста.

В первом примере мы создаем массив a с элементами [1, 2, 3] и переопределяем метод join так, чтобы он ссылался на метод shift. При каждой проверке на равенство с ==, JavaScript вызывает метод toString, который, в свою очередь, вызывает join для массивов. Таким образом, shift удаляет первый элемент массива и возвращает его, что приводит к последовательному возвращению 1, 2 и 3 в выражении console.log(a == 1 && a == 2 && a == 3).

Второй пример использует Symbol.toPrimitive, что является эквивалентом toString и valueOf в ES6. Здесь мы создаем объект a, у которого свойство [Symbol.toPrimitive] переопределяется как функция, увеличивающая счетчик i при каждом вызове. Таким образом, при сравнении console.log(a == 1 && a == 2 && a == 3) происходит то же самое: сначала возвращается 1, затем 2, и наконец 3.

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

2

Ваш код содержит метод get a, который возвращает случайное число от 0 до 3 с использованием Math.random(). Однако, важно отметить, что у вас есть условие, которое проверяет, были ли сгенерированы числа 1, 2 и 3 последовательно в одном и том же проходе цикла.

Тем не менее, текущее условие (if (a == 1 && a == 2 && a == 3)) всегда будет возвращать false, потому что свойство a будет генерироваться заново для каждого вызова в условии, и никогда не сможет одновременно иметь три разных значения на одной итерации.

Если вы хотите проверить, были ли случайно сгенерированы числа 1, 2 и 3 в любой последовательности (может быть не подряд), то вам нужно изменить реализацию. Например, вы можете использовать множество, чтобы хранить уникальные сгенерированные числа:

const generatedNumbers = new Set();

with({
  get a() {
    return Math.floor(Math.random() * 4);
  }
}) {
  for (var i = 0; i < 1000; i++) {
    generatedNumbers.add(a);
    
    if (generatedNumbers.has(1) && generatedNumbers.has(2) && generatedNumbers.has(3)) {
      console.log("after " + (i + 1) + " trials, it becomes true finally!!!");
      break;
    }
  }
}

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

1

Это можно сделать, используя следующий код в глобальной области видимости. Для nodejs вместо window используйте global в приведенном ниже примере.

var val = 0;
Object.defineProperty(window, 'a', {
  get: function() {
    return ++val;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('yay');
}

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

1

Это также можно реализовать с помощью цепочки самоперезаписывающихся геттеров:

(Это похоже на решение пользователя jontro, но не требует переменной-счетчика.)

(() => {
    "use strict";
    Object.defineProperty(this, "a", {
        "get": () => {
            Object.defineProperty(this, "a", {
                "get": () => {
                    Object.defineProperty(this, "a", {
                        "get": () => {
                            return 3;
                        }
                    });
                    return 2;
                },
                configurable: true
            });
            return 1;
        },
        configurable: true
    });
    if (a == 1 && a == 2 && a == 3) {
        document.body.append("Да, это возможно.");
    }
})();

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь