it-swarm.com.ru

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

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

Возможно ли когда-нибудь, чтобы (a== 1 && a ==2 && a==3) мог вычислять true в JavaScript?

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

2331
Dimpu Aravind Buddha

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

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

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


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

3185
Kevin B

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

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

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

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

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

Не делай этого. Шутки в сторону.

Правка: До меня дошло, что (хотя и не разрешено начинать переменную) Столяр с нулевой шириной и Столяр с нулевой шириной Символы также допускаются в именах переменных - см. Запутывание 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!")
}

1969
Jeff

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

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

591
Jonas Wilms

Пример без геттеров или значения:

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);

458
georg

Если его спросить, возможно ли это (не ДОЛЖНО), он может попросить «а» вернуть случайное число. Было бы верно, если бы он генерировал 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;
    }
  }
}

259
mmmaaa

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

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

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

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

203
Kos

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

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

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

186
jontro

Это возможно в случае обращения к переменной 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)

worker.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')
})

modifier.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 на Math.random()*2. Добавление все больше и больше в список снижает вероятность попадания.

182
mehulmpt

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

(Это похоже на решение 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("Yes, it’s possible.");
    }
})();

145
Patrick Dark

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

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

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

a - латинская строчная A
- Прописная латинская строчная A
а - кириллица строчные A

Общий термин для этого - «гомоглифы»: разные символы юникода, которые выглядят одинаково. Обычно трудно получить three, которые совершенно неразличимы, но в некоторых случаях вам может повезти. A, Α, А и Ꭺ будут работать лучше (Latin-A, Greek Alpha , Cyrillic-A и Cherokee-A соответственно; к сожалению, греческий и строчные буквы чероки слишком отличаются от латинского a: α, , и поэтому не помогают с приведенным выше фрагментом).

Существует целый класс атак на гомоглифы, чаще всего в поддельных доменных именах (например, wikipediа.org (кириллица) против wikipedia.org (латиница)), но он также может отображаться в коде; обычно упоминается как закулисный (как упомянуто в комментарии, вопросы [underhanded] теперь не по теме PPCG , но когда-то это был вызов вещей будет отображаться). Я использовал этот сайт , чтобы найти гомоглифы, используемые для этого ответа.

127
Draco18s

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

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!');
}

123
Nina Scholz

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

Питон

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

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

»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, как?

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

Уловка: я использовал скрытый символ после 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

90
RAM

Это перевернутая версия @ ответа Джеффа *, где скрытый символ (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).

78
Salman A

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

Нет необходимости для скрытого обмана персонажа.

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?' );
}

72
MonkeyZeus

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

«Ну, может быть, да при каких-то странных обстоятельствах, которые мне не сразу очевидны ... но если бы я столкнулся с этим в реальном коде, я бы использовал общие методы отладки, чтобы выяснить, как и почему он делает то, что делает и затем немедленно реорганизовать код, чтобы избежать такой ситуации ... но что еще более важно: я бы НИКОГДА не писал этот код в первую очередь, потому что это само определение сложного кода, и я стараюсь никогда не писать сложный код ".

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

66
Frank W. Zammetti

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

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

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

41
Théophile

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

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

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

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

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

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

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

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

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

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

37
Dirk Vollmar

Хорошо, еще один взлом с генераторами:

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!');
}

31
BaggersIO

На самом деле ответ на первую часть вопроса - «Да» на каждом языке программирования. Например, это в случае 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;
}
27
Gustavo Rodríguez

Использование Прокси :

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 приведен к примитивному значению
  4. Приведение этого типа приводит к внутреннему вызову a[Symbol.toPrimitive]()
  5. Прокси перехватывает получение функции a[Symbol.toPrimitive], используя «обработчик get»
  6. «Обработчик получения» Прокси-сервера проверяет, что получаемое свойство имеет 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

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

27
IceCreamYou

То же, но другое, но все же (можно «проверить» несколько раз):

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.

26
Preda7or

Ответ 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 .

23
Omar Alshaker

Я думаю, что это минимальный код для его реализации:

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

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

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

23
Gaafar

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

var _a = 1

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

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

11
Ben Aubin

Переопределив valueOf в объявлении класса, это можно сделать:

class Thing {
    constructor() {
        this.value = 1;
    }

    valueOf() {
        return this.value++;
    }
}

const a = new Thing();

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

Что происходит, так это то, что valueOf вызывается в каждом операторе сравнения. В первом случае a будет равняться 1, во втором a будет равняться 2 и т.д., И т.д., Поскольку каждый раз, когда вызывается valueOf, значение a увеличивается.

Поэтому console.log будет запускать и выводить (в любом случае в моем терминале) Thing: { value: 4}, указывая, что условие было истинным.

0
Jonathan Kuhl