Может ли (a== 1 && a ==2 && a==3) когда-либо оценить значение true?

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

возможно ли, что (a== 1 && a ==2 && a==3) оценить по true в JavaScript?

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

25 ответов


если вы воспользуетесь как == работает, вы можете просто создать объект с пользовательским toString (или valueOf) функция, которая изменяет то, что она возвращает каждый раз, когда она используется так, что она удовлетворяет всем трем условиям.

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

причина, по которой это работает, связана с использованием оператора свободного равенства. При использовании loose equality, если один из операндов имеет другой тип, движок будет пытаться чтобы преобразовать одно в другое. В случае объекта слева и числа справа он попытается преобразовать объект в число, сначала вызвав valueOf если он вызывается, и в противном случае он вызовет toString. Я использовал toString в этом случае просто потому, что это то, что пришло на ум, valueOf было бы больше смысла. Если бы я вместо этого вернул строку из toString, двигатель затем попытался бы преобразовать строку в число, дающее нам тот же конечный результат, хотя и с чуть более длинный путь.


Я не мог устоять - другие ответы, несомненно, верны, но вы действительно не можете пройти мимо следующего кода:

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
    console.log("Why hello there!")
}

обратите внимание на странное расстояние в if заявление (что я скопировал из вашего вопроса). Это половина ширины хангыль (это корейский для тех, кто не знаком), который является символом пространства Юникода, который не интерпретируется сценарием ECMA как символ пространства - это означает, что это допустимый символ для идентификатора. Следовательно есть три совершенно разные переменные, одна с Хангулом после a, одна с ним до и последняя с просто a. Замена пространства на _ для удобства чтения тот же код будет выглядеть следующим образом:

var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
    console.log("Why hello there!")
}

проверить проверка валидатора имени переменной Матиаса. Если этот странный интервал был включен в их вопрос, я уверен, что это намек на такой ответ.

не делай этот. Серьезно.

Edit: мне стало известно, что (хотя и не разрешено запускать переменную)нулевой ширины столяр и Non-joiner нул-ширины символы также разрешены в именах переменных-см. запутывание JavaScript с символами нулевой ширины-плюсы и минусы?.

это будет выглядеть следующим образом:

var a= 1;
var a‍= 2; //one zero-width character
var a‍‍= 3; //two zero-width characters (or you can use the other one)
if(a==1&&a‍==2&&a‍‍==3) {
    console.log("Why hello there!")
}

ЭТО ВОЗМОЖНО!

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.");
  }

пример без геттеров или valueOf:

a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);

это работает, потому что == вызывает toString которых звонки .join для массивов.

другое решение, используя Symbol.toPrimitive что является эквивалентом ES6 toString/valueOf:

let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};

console.log(a == 1 && a == 2 && a == 3);

если его спросят, возможно ли (не обязательно), он может попросить "a" вернуть случайное число. Это будет верно, если он генерирует 1, 2 и 3 последовательно.

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

когда вы ничего не можете сделать без регулярных выражений:

var a = {
  r: /\d/g, 
  valueOf: function(){
    return this.r.exec(123)[0]
  }
}

if (a == 1 && a == 2 && a == 3) {
    console.log("!")
}

он работает из-за custom valueOf метод, вызываемый при сравнении объекта с примитивом (например, число). Главная хитрость в том, что a.valueOf возвращает новое значение каждый раз, потому что он называет exec на регулярном выражении с g флаг, который вызывает обновление lastIndex этого регулярного выражения каждый раз, когда найдено совпадение. Так первый время this.r.lastIndex == 0, соответствует 1 и обновления lastIndex: this.r.lastIndex == 1, так что в следующий раз regex будет соответствовать 2 и так далее.


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

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

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


это возможно в случае переменной a доступ к, скажем, 2 веб-работникам через SharedArrayBuffer, а также некоторые основные скрипты. Возможность низкая, но возможно, что при компиляции кода в машинный код веб-работники обновляют переменную a просто в условиях a==1, a==2 и a==3 удовлетворены.

это может быть примером состояния гонки в многопоточной среде, предоставляемой веб-работниками и SharedArrayBuffer в JavaScript.

вот основная реализация выше:

main.js

// Main Thread

const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let's use 2 workers
const sab = new SharedArrayBuffer(1)

modifiers.forEach(m => m.postMessage(sab))
worker.postMessage(sab)

работника.js

let array

Object.defineProperty(self, 'a', {
  get() {
    return array[0]
  }
});

addEventListener('message', ({data}) => {
    array = new Uint8Array(data)
    let count = 0
    do {
        var res = a == 1 && a == 2 && a == 3
        ++count
    } while(res == false) // just for clarity. !res is fine
    console.log(`It happened after ${count} iterations`)
    console.log('You should\'ve never seen this')
})

модификатор.js

addEventListener('message' , ({data}) => {
    setInterval( () => {
        new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
    })
})

на моем MacBook Air это происходит после примерно 10 миллиардов итераций с первой попытки:

enter image description here

вторая попытка:

enter image description here

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

совет: если это занимает слишком много времени в системе. Попробуйте только a == 1 && a == 2, и Math.random()*3 to Math.random()*2. Добавление все больше и больше в список снижает вероятность попадания.


Это также возможно с помощью серии геттеров самозаписи:

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

(() => {
    "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("Yes, it’s possible.");
    }
})();

Я не вижу, что этот ответ уже опубликован, поэтому я тоже брошу его в микс. Это похоже на Джефф!--10--> с космосом хангыля половин-ширины.

var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
    console.log("Why hello there!")
}

вы можете заметить небольшое несоответствие со вторым, но первый и третий идентичны невооруженным глазом. Все 3 различных символов:

a - латинский нижний регистр a
- полная ширина латинского нижнего регистра a
а - Кириллицы в нижнем регистре с

общий термин для этого является "homoglyphs": разных символов Unicode, которые выглядят одинаково. Как правило, трудно получить три это совершенно неразличимо, но в некоторых случаях вам может повезти. A, Α, А и Ꭺ работали бы лучше (латинский-A,Греческий Alpha, Кириллица-А и Чероки-Это соответственно; к сожалению, греческие и чероки строчные буквы слишком отличаются от латинских a: α,, and so doesn't help with the above snippet).

There's an entire class of Homoglyph Attacks out there, most commonly in fake domain names (eg. wikipediа.org (кириллица) vs wikipedia.org (латинский)), но он также может отображаться в коде; обычно упоминается как коварный (как указано в комментарии,[коварно] вопросы теперь вне темы на PPCG, но раньше был тип вызова, где такие вещи будут отображаться). Я использовал этот сайт найти homoglyphs использовать для этого ответа.


кроме того, вы можете использовать класс для него и экземпляр для проверки.

function A() {
    var value = 0;
    this.valueOf = function () { return ++value; };
}

var a = new A;

if (a == 1 && a == 2 && a == 3) {
    console.log('bingo!');
}

редактировать

используя классы ES6, это будет выглядеть так

class A {
  constructor() {
    this.value = 0;
    this.valueOf();
  }
  valueOf() {
    return this.value++;
  };
}

let a = new A;

if (a == 1 && a == 2 && a == 3) {
  console.log('bingo!');
}

JavaScript

a == a +1

в JavaScript нет чисел, но только Numbers, которые реализованы как числа с плавающей запятой двойной точности.

это означает, что если количество a достаточно большой, его можно считать равным трем последовательным целым числам:

a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
  console.log("Precision loss!");
}

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

другие языки

Для справки, есть a==1 && a==2 && a==3 решения в Ruby и Python. С небольшой модификацией, также возможно в Java.

Рубин

с пользовательским ==:

class A
  def ==(o)
    true
  end
end

a = A.new

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

или увеличение a:

def a
  @a ||= 0
  @a += 1
end

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

Python

class A:
    def __eq__(self, who_cares):
        return True
a = A()

if a == 1 and a == 2 and a == 3:
    print("Don't do that!")

Java

можно изменить Java Integer кэш:

package stackoverflow;

import java.lang.reflect.Field;

public class IntegerMess
{
    public static void main(String[] args) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.setInt(1, valueField.getInt(42));
        valueField.setInt(2, valueField.getInt(42));
        valueField.setInt(3, valueField.getInt(42));
        valueField.setAccessible(false);

        Integer a = 42;

        if (a.equals(1) && a.equals(2) && a.equals(3)) {
            System.out.println("Bad idea.");
        }
    }
}

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

" JavaScript

if‌=()=>!0;
var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    document.write("<h1>Yes, it is possible!</h1>")
}

приведенный выше код является короткой версией (спасибо @Forivin за его примечание в комментариях), и следующий код является оригинальным:

var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    //console.log("Yes, it is possible!")
    document.write("<h1>Yes, it is possible!</h1>")
}

//--------------------------------------------

function if‌(){return true;}

если вы просто видите верхнюю часть моего кода и запускаете его, вы говорите WOW, как?

поэтому я думаю, что достаточно сказать Да, это возможно кому-то, кто сказал вы: нет ничего невозможного

Trick: я использовал скрытый символ после if чтобы сделать функцию, что ее имя похоже на if. В JavaScript мы не можем переопределять ключевые слова, поэтому я вынужден использовать этот способ. Это подделка if, но это работает для вас в таком случае!


" C#

также я написал версию C# (С увеличением стоимости имущества техника):

static int _a;
public static int a => ++_a;

public static void Main()
{
    if(a==1 && a==2 && a==3)
    {
        Console.WriteLine("Yes, it is possible!");
    }
}

Live Demo


это перевернутая версия @Джефф* где скрытый символ (U + 115F, U + 1160 или U+3164) используется для создания переменных, которые выглядят как 1, 2 и 3.

var  a = 1;
var ᅠ1 = a;
var ᅠ2 = a;
var ᅠ3 = a;
console.log( a ==ᅠ1 && a ==ᅠ2 && a ==ᅠ3 );

* этот ответ можно упростить, используя нулевую ширину без столяра (U+200C) и нулевую ширину столяра (U+200D). Оба этих символа разрешены внутри идентификаторов, но не в начало:

var a = 1;
var a‌ = 2;
var a‍ = 3;
console.log(a == 1 && a‌ == 2 && a‍ == 3);

/****
var a = 1;
var a\u200c = 2;
var a\u200d = 3;
console.log(a == 1 && a\u200c == 2 && a\u200d == 3);
****/

другие трюки возможны с использованием той же идеи, например, с помощью селекторов вариантов Unicode для создания переменных, которые выглядят точно так же (a︀ = 1; a︁ = 2; a︀ == 1 && a︁ == 2; // true).


правило номер один интервью; никогда не говори невозможно.

нет необходимости в скрытом обмане персонажа.

window.__defineGetter__( 'a', function(){
    if( typeof i !== 'number' ){
        // define i in the global namespace so that it's not lost after this function runs
        i = 0;
    }
    return ++i;
});

if( a == 1 && a == 2 && a == 3 ){
    alert( 'Oh dear, what have we done?' );
}

честно говоря, есть ли способ оценить его на истинный или нет (и, как показали другие, есть несколько способов), ответ, который я бы искал, говоря как кто-то, кто провел сотни интервью, будет что-то вроде:

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


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

const a = {
  n: [3,2,1],
  toString: function () {
    return a.n.pop();
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Yes');
}

Если вы когда-нибудь получите такой вопрос интервью (или заметите какое-то столь же неожиданное поведение в вашем коде), подумайте о том, какие вещи могут вызвать поведение, которое выглядит невозможным на первый взгляд:

  1. кодирование: в этом случае переменная, на которую вы смотрите, не та, о которой вы думаете. Это может произойти, если вы намеренно возитесь с Unicode, используя homoglyphs или Пространство символов сделать имя переменной выглядит как другое, но проблемы с кодированием также могут быть введены случайно, например, при копировании и вставке кода из Интернета, который содержит неожиданные кодовые точки Unicode (например, потому, что система управления контентом сделала некоторое "автоматическое форматирование", такое как замена fl с юникодом 'Латинская малая лигатура FL' (U+FB02)).

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

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

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

  3. побочные эффекты: побочный эффект операции сравнения равенства (который не должен быть столь очевидным, как в примерах здесь, часто побочные эффекты очень тонкие).

такие проблемы могут появляться на многих языках программирования, а не только на JavaScript, поэтому мы не видим ни одного из классических JavaScript WTFs здесь1.

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

  • побочные эффекты могут стать очень неприятными и что хорошо разработанная программа должна быть свободна от нежелательных побочные явления.
  • многопоточное и изменяемое состояние может быть проблематичным.
  • не делать кодировку символов и строку обработки право может привести к неприятным ошибкам.

1 например, вы можете найти пример на совершенно другом языке программирования (C#), демонстрирующий побочный эффект (очевидный)здесь.


ладно, еще один хак с генераторами:

const value = function* () {
  let i = 0;
  while(true) yield ++i;
}();

Object.defineProperty(this, 'a', {
  get() {
    return value.next().value;
  }
});

if (a === 1 && a === 2 && a === 3) {
  console.log('yo!');
}

на самом деле ответ на первую часть вопроса "Да" на любом языке программирования. Например, это в случае C / C++:

#define a   (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
    std::cout << "Yes, it's possible!" << std::endl;
} else {
    std::cout << "it's impossible!" << std::endl;
}

то же самое, но другое, но все же то же самое (может быть "проверено" несколько раз):

const a = { valueOf: () => this.n = (this.n || 0) % 3 + 1}
    
if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

моя идея началась с того, как работает уравнение типа объекта Number.


используя Прокси:

var a = new Proxy({ i: 0 }, {
    get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name],
});
console.log(a == 1 && a == 2 && a == 3);

прокси в основном притворяются целевым объектом (первый параметр), но перехватывают операции над целевым объектом (в этом случае операция "получить свойство"), так что есть возможность сделать что-то другое, чем поведение объекта по умолчанию. В этом случае действие "get property" вызывается на a, когда == принуждает его тип для того, чтобы сравнить его с каждым номером. Это происходит:

  1. мы создать целевой объект, { i: 0 }, где i свойство наш счетчик
  2. мы создаем прокси для целевого объекта и присвоить его a
  3. для каждого a == сравнения aтип s принуждается к примитивному значению
  4. этот тип принуждения приводит к вызову a[Symbol.toPrimitive]() внутри
  5. Прокси перехватывает получение a[Symbol.toPrimitive] функция с помощью "GET handler"
  6. "обработчик get" прокси-сервера проверяет, что свойство получить это Symbol.toPrimitive, в этом случае он увеличивает, а затем возвращает счетчик из целевого объекта:++target.i. Если другое свойство извлекается, мы просто вернемся к возвращению значения свойства по умолчанию,target[name]

так:

var a = ...; // a.valueOf == target.i == 0
a == 1 && // a == ++target.i == 1
a == 2 && // a == ++target.i == 2
a == 3    // a == ++target.i == 3

как и большинство других ответов, это работает только с свободной проверкой равенства (==), потому что строгое равенство проверяет (===) не делайте принуждение типа, которое прокси может перехватить.


Я думаю, что это минимальный код для ее осуществления:

i=0,a={valueOf:()=>++i}

if (a == 1 && a == 2 && a == 3) {
  console.log('Mind === Blown');
}

создание фиктивного объекта с пользовательским valueOf это увеличивает глобальную переменную i при каждом вызове. 23 символов!


ответ ECMAScript 6, который использует символы:

const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));

из-за == использование, JavaScript должен принудить a В что-то близкое ко второму операнду (1, 2, 3 в данном случае). Но прежде чем JavaScript попытается вычислить принуждение самостоятельно, он попытается вызвать Symbol.toPrimitive. Если вы предоставите Symbol.toPrimitive JavaScript будет использовать значение, которое возвращает ваша функция. Если нет, JavaScript вызовет valueOf.


этот использует defineProperty с хорошим побочным эффектом, вызывающим глобальную переменную!

var _a = 1

Object.defineProperty(this, "a", {
  "get": () => {
    return _a++;
  },
  configurable: true
});

console.log(a)
console.log(a)
console.log(a)