Недавно по Твиттеру и Реддиту гулял интересный кусок кода на JavaScript. Вопрос, связанный с ним, заключался в следующем: «Может ли выражение (a==1 && a==2 && a==3) вернуть true?». Ответ на вопрос, как ни странно, был положительным.

image

Сегодня мы разберём этот код и постараемся его понять.

Вот он:

const a = {
  num: 0,
  valueOf: function() {
    return this.num += 1
  }
};
const equality = (a==1 && a==2 && a==3);
console.log(equality); // true

Если вы используете Google Chrome, откройте консоль инструментов разработчика с помощью комбинации клавиш Ctrl + Shift + J в Windows, или Cmd + Opt + J в macOS. Скопируйте этот код, вставьте в консоль и убедитесь в том, что на выходе и правда получается true.

В чём тут подвох?


На самом деле, ничего удивительного тут нет. Просто этот код использует две базовые концепции JavaScript:

  • Оператор нестрогого равенства.
  • Метод объекта valueOf().

Оператор нестрогого равенства


Обратите внимание на то, что в исследуемом выражении, (a==1 && a==2 && a==3), применяется оператор нестрогого равенства. Это означает, что в ходе вычисления значения этого выражения будет использоваться приведение типов, то есть, с помощью == сравнивать можно значения разных типов. Я уже много об этом писал, поэтому не буду тут вдаваться в подробности. Если вам нужно вспомнить особенности работы операторов сравнения в JS — обратитесь к этому материалу.

Метод valueOf()


В JavaScript имеется встроенный метод для преобразования объекта в примитивное значение: Object.prototype.valueOf(). По умолчанию этот метод возвращает объект, для которого он был вызван.

Создадим объект:

const a = {
  num: 0
}

Как сказано выше, когда мы вызываем valueOf() для объекта a, он просто возвращает сам объект:

a.valueOf();
// {num: 0}

Кроме того, мы можем использовать typeOf() для проверки того, действительно ли valueOf() возвращает объект:

typeof a.valueOf();
// "object"

Пишем свой valueOf()


Самое интересное при работе с valueOf() заключается в том, что мы можем этот метод переопределить для того, чтобы конвертировать с его помощью объект в примитивное значение. Другими словами, можно использовать valueOf() для возврата вместо объектов строк, чисел, логических значений, и так далее. Взгляните на следующий код:

a.valueOf = function() {
  return this.num;
}

Здесь мы заменили стандартный метод valueOf() для объекта a. Теперь при вызове valueOf() возвращается значение a.num.

Всё это ведёт к следующему:

a.valueOf();
// 0

Как видно, теперь valueOf() возвращает 0! Самое главное здесь то, что 0 — это то значение, которое назначено свойству объекта a.num. Мы можем в этом удостовериться, выполнив несколько тестов:

typeof a.valueOf();
// "number"
a.num == a.valueOf()
// true

Теперь поговорим о том, почему это важно.

Операция нестрогого равенства и приведение типов


При вычислении результата операции нестрогого равенства для операндов различных типов JavaScript попытается произвести приведение типов — то есть он сделает попытку привести (конвертировать) операнды к похожим типам или к одному и тому же типу.

В нашем выражении, (a==1 && a==2 && a==3), JavaScript попытается привести объект a к числовому типу перед сравнением его с числом. При выполнении операции приведения типа для объекта JavaScript, в первую очередь, попытается вызвать метод valueOf().

Так как мы изменили стандартный метод valueOf() так, что теперь он возвращает значение a.num, которое является числом, теперь мы можем сделать следующее:

a == 0
// true

Неужто задача решена? Пока нет, но осталось — всего ничего.

Оператор присваивания со сложением


Теперь нам нужен способ систематически увеличивать значение a.num каждый раз, когда вызывается valueOf(). К счастью, в JavaScript есть оператор присваивания со сложением, или оператор добавочного присваивания (+=).

Этот оператор просто добавляет значение правого операнда к переменной, которая находится слева, и присваивает этой переменной полученное значение. Вот простой пример:

let b = 1
console.log(b+=1); // 2
console.log(b+=1); // 3
console.log(b+=1); // 4

Как видите, каждый раз, когда мы используем оператор присваивания со сложением, значение переменной увеличивается! Используем эту идею в нашем методе valueOf():

a.valueOf = function() {
  return this.num += 1;
}

Вместо того чтобы просто возвращать this.num, мы теперь, при каждом вызове valueOf(), будем возвращать значение this.num, увеличенное на 1 и записывать новое значение в this.num.

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

const equality = (a==1 && a==2 && a==3);
console.log(equality); // true

Работает!

Пошаговый разбор


Помните о том, что при использовании оператора нестрогого равенства JS пытается выполнить приведение типов. Наш объект вызывает метод valueOf(), который возвращает a.num += 1, другими словами, возвращает значение a.num, увеличенное на единицу при каждом его вызове. Теперь остаётся лишь сравнить два числа. В нашем случае все сравнения выдадут true.

Возможно, полезно будет рассмотреть происходящее пошагово:

a                     == 1   -> 
a.valueOf()           == 1   -> 
a.num += 1            == 1   -> 
0     += 1            == 1   ->
1                     == 1   -> true
a                     == 2   -> 
a.valueOf()           == 2   -> 
a.num += 1            == 2   -> 
1     += 1            == 2   ->
2                     == 2   -> true
a                     == 3   -> 
a.valueOf()           == 3   -> 
a.num += 1            == 3   -> 
2     += 1            == 3   ->
3                     == 3   -> true

Итоги


Полагаем, примеры, подобные разобранному выше, помогают, во-первых, лучше изучить базовые возможности JavaScript, а во-вторых — не дают забыть о том, что в JS не всё является тем, чем кажется.

Уважаемые читатели! Если вы знаете о каких-нибудь курьёзах из области JavaScript — просим ими поделиться.

Комментарии (92)


  1. vesper-bot
    25.01.2018 16:12

    stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true тут больше вариантов добиться такого поведения.


  1. poxvuibr
    25.01.2018 16:25
    +1

    Может ли в JavaScript конструкция (a==1 && a==2 && a==3) оказаться равной true?

    Чего тут думать, в джаваскрипте любая конструкция с участием == может оказаться равной чему угодно :)


    1. id_potassium_chloride
      25.01.2018 16:56

      Я бы сказал, в JS почти любая конструкция может дать почти любой результат :)


      1. Fen1kz
        25.01.2018 17:36

        void false;


    1. PavelDymkov
      26.01.2018 16:51
      +1

      Из комментария выше можно выделить два метода: с .valueOf и совсем отмороженный с with. Так вот второй работает и с ===:

      with({
          _a: 0,
          get a() {
              return ++this._a;
          }
      }) {
          console.log(a === 1 && a === 2 && a === 3); // true
      }
      


  1. infrapro
    25.01.2018 16:30
    +1

    Почему в заголовке фигурирует только JavaScript? Такого же поведения можно добиться и в других языках. Вопрос только какого типа 'a'?

    В C++ это, например, можно сделать так
    #include <iostream>
    
    class A {
        int _a;
    public:
        A():_a(0) {};
        ~A() {};
        
        bool operator ==(int i)
        {
            return ++_a == i;
        }
    };
    
    int main(int argc, const char * argv[]) {
        
        A a;
        if (a == 1 && a == 2 && a == 3) {
            std::cout << "Hello, World!\n";
        }
        return 0;
    }


    1. mayorovp
      25.01.2018 16:59
      +3

      В С++ все еще проще:


      struct {
          bool operator ==(int i)
          {
              return true;
          }
      } a;


    1. devalone
      25.01.2018 17:07

      Так по красивее :)

      #include <iostream>
      
      #include "happy_debugging_lol.h"
      
      auto main() -> decltype(0)
      {
          int a = 1;
      
          if (a == 1 && a == 2 && a == 3)
              std::cout << "WTF? Why am I seeing this?" << std::endl;
      }
      

      Не верите, что это работает? Вот содержимое файла happy_debugging_lol.h
      #pragma once
      
      class FakeInt {
      public:
          FakeInt(int value)
              : value(value)
          {
          }
          operator int() const
          {
              return value;
          }
          template <typename T>
          bool operator==(const T& other) const
          {
              return true;
          }
      
      private:
          int value;
      };
      #define int FakeInt
      
      


      1. TimeCoder
        25.01.2018 18:22

        operator int() const

        — вроде лишнее? Нет же в коде приведения к типу int.


        1. devalone
          25.01.2018 18:24

          Ну это я так на всякий случай написал, чтоб он был более похож на int по интерфейсу, конечно ещё надо реализовать операторы сложения, умножения и т.д.


      1. shybovycha
        26.01.2018 03:26

        а чем объявление


        auto main() -> decltype(0)

        красивее


        int main()

        ???


        1. mayorovp
          26.01.2018 06:26

          Тем, что int переопределен, а decltype(0) — нет :-)


        1. devalone
          26.01.2018 14:05

          Оно не красивее, просто в заголовочном файле стоит #define int FakeInt и препроцессор заменяет все вхождения int в том числе и int main() меняется на FakeInt main(), поэтому я сделал так, можно было ещё поменять на int32_t, например. Хотя сейчас посмотрел, можно было даже так

          decltype(0) main()

          Или так:
          Int main()

          и в заголовочном файле перед define добавить:
          using Int = int;


          1. khim
            26.01.2018 14:14

            Да можно даже signed main написать! Но в любом случае отличие от «стандартной» формы должно насторожить…


  1. devalone
    25.01.2018 16:39

    А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.


    1. Pilat
      25.01.2018 16:44

      Всё-же нарваться на такой пример неприятно:


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


      1. devalone
        25.01.2018 16:56

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


        1. Pilat
          25.01.2018 20:26
          -1

          Да с пол-пинка. Копипаст откуда-нибудь. Никогда не встречали в интернет-формах банков "не вводите номер копипастом заполняйте руками"? Я такое встречаю регулярно и часто так и получается — копипаст почему-то переносит неправильно.


      1. Alexanqp
        26.01.2018 08:09

        Объясните пожалуйста данный пример, почему так? Спасибо.


        1. mayorovp
          26.01.2018 08:25

          Это пример из ответа на SO: stackoverflow.com/a/48274520/4340086

          Объяснение есть там же.


        1. Pilat
          26.01.2018 09:10

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


      1. bro-dev
        26.01.2018 09:11

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


  1. Kopleman
    25.01.2018 16:56

    На всех собеседованиях этой страны :)


  1. nick_gabpe
    25.01.2018 16:56

    Достаточно баянистый, но тем не менее любопытный пример:

    '5'-3        // выводит 2
    '5'+3       // выводит "53"     
    '5'+-3      // выводит "5-3"
    


    1. A1essandro
      25.01.2018 17:06
      -1

      Мой любимый пример, когда я начинаю объяснять, почему мне не нравится JS (и вообще слабая типизация). Это, возможно, лично мое мнение, но python (как пример динамической типизации) со своей сильной типизацией, и, как следствие, невозможностью выполнения такого кода — менее подвержен ошибкам.


      1. alex6636
        25.01.2018 22:49
        -2

        В последнее время даже не пытаюсь объяснить, просто говорю js sucks


      1. Keyten
        26.01.2018 01:48
        +1

        И часто вы вычитаете число из строки?
        Нет, ну правда. За годы, что я пишу на js, у меня сформировалась в голове ide, которая следит за всеми типами и предупреждает, если я складываю или вычитаю что-то, что может быть строкой.
        А писать явное преобразование в Number / String везде, где может быть ошибка, вам вообще ничего не мешает. Как вы делаете это на других языках. Разве что тут вы это можете проигнорировать, а там — нет.


        1. A1essandro
          26.01.2018 12:09

          (В профиле написано, «можно и нужно обращаться на ты»)
          Ты привязываешься к конкретному примеру? Я в целом говорю, что это возможно (как и к массиву прибавить интовую 1, и внезапно получить строковую «1»), и считаю это неправильным. Если программист действительно хочет к строке «прибавить» число, тогда он должен явно указать, он хочет получить сумму чисел или произвести конкатенацию строк (т.е. один операнд привести к типу второго).

          А по поводу часто или нет. Нет, ну серьезно, скорее всего такое нечасто встречается в отполированном коде. Но в момент, когда идет действительно очень активная работа с кодом, когда над тобой давлеет дедлайн — можно по ошибке что-то не туда скопипастить, либо просто не туда вписать. Программист — не машина, иногда он теряет концентрацию и может допустить глупую ошибку. Но JS в этом случае не помогает говоря: "парень, вот тут ты уточни, ты действительно так хотел написать?". Он просто проходит некорректный участок кода, как будто так и надо, говоря: "это абсолютно не мои проблемы, я просто к пустому массиву прибавлю число, и верну строку.".


        1. justboris
          26.01.2018 12:11

          И часто вы вычитаете число из строки?

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


          const a = 0.1;
          const x = document.querySelector('input[name="x"]').value;
          const y = document.querySelector('input[name="y"]').value;
          
          console.log(x - a * y)

          a*y скастуется в число, а x останется строкой.


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


          1. SuperPaintman
            26.01.2018 17:11

            Ну и сами вы себе злой буратино. Зная, что в JS такое поведение, и что input value — это всегда строка, кто мешает вам, опять же зная, что вы будете производить мат. операции над этим инпутом, самому сконвертировать.


            const a = 0.1;
            const x = +document.querySelector('input[name="x"]').value; // Вот теперь это число
            const y = +document.querySelector('input[name="y"]').value; // И это тоже
            
            console.log(x - a * y)

            Хотя parseInt(n, 10) все-же более наглядно и конвертирует исключительно в десятичное число (т.к. +'0xFF' === 255).


            По-хорошему, нужно x и y сконвертировать в число в момент присвоения

            С чего бы это? Инпут хранит исключительно значение как строку, а если вы про input[type="number"] — то это не имеет никакого отношения к JS, это лишь валидация пользовательского инпута, как и type="email", а в JS это приходит как обычная DOMString.


            1. SuperPaintman
              26.01.2018 17:18

              И нет, "a" — не останется строкой, т.к. "-" тоже кастует в Number


              1. SuperPaintman
                26.01.2018 17:33

                (простите, опечатался, не "a", а конечно же "x" станет числом тоже).


                И в целом то, это проблема не JS, а вообще динамических языков, и языков с хоть чуть-чуть возможностью переопределять операторы или (и) неявно приводить типы.


                # Python
                >>> "5" * 10 + "8"
                '55555555558'

                Т.е. тут вопрос скорее к невнимательности. И тут TS или Flow, бы решили эту проблему (ну или попытались :) ).


                1. SuperPaintman
                  26.01.2018 17:43

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


                1. mayorovp
                  26.01.2018 18:54

                  Кстати, хорошо что вы Python упомянули. Дело в том, что этот язык — с сильной типизацией, а не со слабой как javascript. И все равно в нем, как видно, можно забыть о типах и насчитать чепуху.

                  Более того, аналогичную ерунду можно получить на любом языке с возможностью перегрузки операторов — даже на статически типизированных C++ или C# :-)


                  1. A1essandro
                    26.01.2018 21:07

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

                    var a = "5"+1;

                    тоже корректный код в C#, но только с оператором "+" и только если один из операндов — строка. Вот так уже нельзя:
                    int a = "5" + 1;

                    Т.е. всё это дело приводится к строке (toString()). Даже так можно написать:
                    var a = "5"+new Object();

                    Но это, как я полагаю, сделано для читабельного кода при конкатенации строк с «нестроками».


            1. justboris
              26.01.2018 17:39

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


              1. SuperPaintman
                26.01.2018 17:48

                Вы абсолютно правы, что язык должен в первую очередь быть инструментом, а не целью. Но в большинстве языков: Python — input(), C — getchar(), Ruby — gets, инпут — это строка.


                Так и в JS, input Dom node — содержит исключительно строки.


                1. justboris
                  26.01.2018 18:12

                  Именно поэтому я бы перевел код на Typescript (рабочий пример).


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


                  1. SuperPaintman
                    26.01.2018 18:30

                    Тут трудно не согласиться (я об это тоже сказал чуть выше). Но мы все-же говорим в контексте JS.


                    Собственно, надеюсь, я правильно понял вашу позицию. TS действительно крутой :).


                  1. mayorovp
                    26.01.2018 18:59

                    Тем не менее, document.getElementById('layer1').style.opacity += 0.01 из обсуждения ниже в Typescript успешно скомпилируется и сделает ерунду. Более того, аналогичный глупый код скорее всего скомпилируется даже на C# или Java, а может даже на C++ (тут уже смотря как библиотека будет сделана).


                    1. SuperPaintman
                      26.01.2018 20:03

                      Да, т.к. document.getElementById('layer1').style.opacity — это строка, а не число.


                      И формально, это равносильно этому:


                      let a = '0.2';
                      
                      a += 0.01; // Тут Number преобразуется в строку
                      
                      console.log(a); // => 0.20.01

                      Но тут уже важно понимание дела, т.е. TS не знает, хотите ли вы добавить символы в строку, или сложить числа.


                      И Flow сделает то-же самое: https://flow.org/try/#0DYUwLgBAhhC8EHICMCDcAodMDU8BMG6AxgPYB2AziaAHTAkDmAFFAJSoQD0ncAfBEjxA


                      1. mayorovp
                        26.01.2018 20:24

                        О чем я и говорю. Статическая типизация — вещь хорошая, но не панацея.


                    1. justboris
                      27.01.2018 14:58

                      На это уже есть фича-реквест: https://github.com/Microsoft/TypeScript/issues/7989


                      Пока в сам Typescript такое ограничение не добавили, предлагается использовать TSLint-правило: https://palantir.github.io/tslint/rules/restrict-plus-operands/


            1. TheShock
              27.01.2018 00:00

              Хотя parseInt(n, 10) все-же более наглядно.

              Мне больше всего нравится Number(n)


      1. nick_gabpe
        26.01.2018 18:11

        Согласен. Правда в Python есть свои странности :)


  1. A1essandro
    25.01.2018 16:56

    На C# свойства тоже позволяют так делать:

            private static int _a = 0;
            private static int a { get => ++_a; }
    
            static void Main(string[] args)
            {
                if (a == 1 && a == 2 && a == 3)
                {
                    Console.WriteLine("True");
                }
                Console.Read();
            }
    


    1. mayorovp
      25.01.2018 17:01

      Только все-таки лучше писать private static int a => ++_a;


      1. alkozko
        25.01.2018 18:50
        +2

        лучше вообще такое не писать никогда


        1. A1essandro
          25.01.2018 21:24

          alkozko, я с Вами полностью согласен. Как и с комментариемdevalone:

          А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.

          Просто хотел подчеркнуть, что тема актуальна не только для JS. И уж точно не является руководством к действию.


        1. GraDea
          25.01.2018 22:25

          Почему столь категорично? У нас есть что-то похожее в тестовом DSL как заменитель автоикремента в БД.


          1. mayorovp
            25.01.2018 23:20

            Нарушается семантика свойства. Свойство — это "эмулятор поля", предполагается что его значение не должно меняться просто от факта обращения к нему.


            Вот метод вида int a() => ++_a; — это нормально. А подобное свойство — нет.


    1. impwx
      25.01.2018 20:59
      +1

      Есть способ гораздо проще и стабильнее:

      class MyClass
      {
          public static bool operator ==(MyClass left, int right) => true;
          public static bool operator !=(MyClass left, int right) => true;
      }
      
      var a = new MyClass();
      if(a == 1 && a == 2 && a == 3)
          Console.WriteLine("Equals");
      


  1. zawodskoj
    25.01.2018 17:03
    -1

    #include <stdio.h>
    
    #define a (__COUNTER__+1)
    
    int main(void) {
        if (a == 1 && a == 2 && a == 3) printf("123123123");
        return 0;
    }


    1. SuperPaintman
      25.01.2018 17:53

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


      Для начала, это абсолютно валидный код с точки зрения C.


      #define директива — это по-сути замена во время компиляции найденого токена (или вызова) на его значени. В случае выше, любая найденная a будет заменена на (__COUNTER__+1)


      __COUNTER__ — это "магическое" макро значение, как и __FILE__, __LINE__, или __FUNCTION_. И в C / C++ эти значения опять же заменяются на этапе компиляции (см. выше). Сам же __COUNTER__ "магичен" из-за того, что при каждом новом вызове, он будет инкриментировать свое значение (от 0).
      В итоге выражение (__COUNTER__+1) станет (0+1), затем (1+1), затем (2+1).


      И весь код будет выглядеть так:


      // Т.к. `include` тоже макрос, тут будет содержимое этого файла.
      
      int main(void) {
          if ((0+1) == 1 && (1+1) == 2 && (2+1) == 3) printf("123123123");
          return 0;
      }

      А дальше все еще круче, если кому интересно, компилятор просто схлопнет (оптимизирует) константные выражения, и на выходе останется:


      // Т.к. `include` тоже макрос, тут будет содержимое этого файла.
      
      int main(void) {
          printf("123123123");
          return 0;
      }

      Так-что не спешите кидаться на zawodskoj


      1. zawodskoj
        25.01.2018 17:55

        А заминусовали меня скорее всего потому, что я скопипастил этот код из своего ideone, который содержал слово, которое даже матом не считается, и не успел быстро отредактировать.
        Первый минус проставил тот, кто увидел, а второй кто-то «НУ ПОТОМУ ЧТО ТУТ МИНУС СТОИТ, ЗНАЧИТ ПЛОХОЙ КОММЕНТ, НАДО ТОЖЕ МИНУСНУТЬ»
        Вот и пиши после этого комменты вообще


        1. mayorovp
          25.01.2018 18:44
          -2

          Если бы все было как вы написали — то и двух плюсов вы бы не получили.

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


      1. devalone
        25.01.2018 17:58

        Забей, я как-то написал, что правильно не «Силиконовая долина», а «Кремниевая долина» и меня заминусили(самые обиженные даже в карму пошли минусовать).


        1. fireSparrow
          26.01.2018 11:52
          -1

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


        1. Ernest88
          26.01.2018 12:33

          Это какая-то местная фишка, минусовать)) Плюсовать в карму никто не идет почему-то ))


          1. mayorovp
            26.01.2018 12:33

            А нельзя плюсовать выше +4 если нет публикаций.


      1. mayorovp
        25.01.2018 18:42
        -2

        Я ему минус за мат ставил, сейчас мата в комментарии уже нет.


    1. thedsi666
      26.01.2018 01:06

      __COUNTER__, несмотря на поддержку многими компиляторами, не входит в стандарт языка (к сожалению).


  1. LioneNET
    25.01.2018 17:52

    Извините за очень нескромный вопрос, но во всех ли интерпретаторах проверка будет идти слева на право?


    1. SuperPaintman
      25.01.2018 17:57

      Если интерпретатор написан по-стандарту (и здравому смыслу), && будут идти слева-направо. V8 / SpiderMonkey / Chakra работают именно так


    1. gearbox
      25.01.2018 22:50

      здравый смысл не нужен, приоритет операторов и ассоциативность. developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Operator_Precedence что интересно — в стандарте ecma не нашел ни ассоциативности ни приоритетов.


  1. KodyWiremane
    25.01.2018 18:49
    +1

    «В JS нельзя true = false, поэтому если хотите отомстить коллегам — вот пара идей..»


  1. artur93gev
    25.01.2018 22:32

    можно просто
    b = 0;
    Object.defineProperty(window, 'a', {
    set: (v) => {

    },
    get: () => {
    return b++;
    }
    });


    1. iShatokhin
      26.01.2018 20:52

      Или даже так:

      const a = {
        num: 0,
        [Symbol.toPrimitive]: function() {
          return ++this.num;
        }
      };
      


  1. mirypoko
    25.01.2018 22:32

    Я думаю так можно на любом языке, например на C#

    class A
        {
            private int a = 0;
    
            public static implicit operator Int32(A o)
            {
                o.a++;
                return o.a;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                var a = new A();
                if (a == 1 && a == 2 && a == 3)
                {
                    Console.WriteLine("Неожиданный поворот");
                }
                Console.WriteLine("Hello World!");
            }
        }


  1. symbix
    25.01.2018 22:36

    Зашел увидеть эту ссылку — и не нашел. Пусть тут полежит:


    Return true to win


    Предупреждение: not safe for work! Не в том смысле, а просто работа может остановиться :-)


  1. JorryGo
    26.01.2018 12:02
    -1

    Ну и php, куда же без него.

    $a = true;
    if ($a == 1 && $a = 2 && $a = 3 ) {
        echo 'I am here';
    }


    1. alcanoid
      26.01.2018 16:54
      +1

      У вас в условии два присваивания. Но да, если бы вместо них были сравнения, выражение по-прежнему было бы истинно.


  1. Ernest88
    26.01.2018 12:05
    -2

    Не покидает ощущение, что JavaScript был когда-то осквернен женской логикой… Ведет он себя совсем не по машинному… Еще чуть-чуть и станет выдавать ошибки наподобии «ой, всее...»


    1. justboris
      26.01.2018 12:14
      +1

      Причем здесь Javascript?


      Выше в комментариях смогли реализовать такой же пример на С++ и C#. Если этого мало, вот тут еще есть варианты на Ruby, Python и Java.


      1. Ernest88
        26.01.2018 14:08
        -1

        Тема поста у нас JS. Это мое ощущение языка, как дилетанта. Конкретный пример тут не при чем. Я пробовал писать простенькие вещи в проядке ознакомления на некоторых языках и только JS пока мне показался таким туманным, чтоли, со своими типами. Очевидные вещи не хотели работать, примерно: а = 2 + 2; иф (а == 4) зен не выполнялось, так как а == 4,0000000001. Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.


        1. faiwer
          26.01.2018 14:15

          а = 2 + 2; иф (а == 4) зен не выполнялось, так как а == 4,0000000001

          А вам это не приснилось? В JS обыкновенные double числа. Никакой магии. Пока вы работаете с целой частью — проблем нет. Начинаете работать с дробной — привет степени двойки и прочие радости double. В принципе, всё как и везде.


          According to the ECMAScript standard, there is only one number type: the double-precision 64-bit binary format IEEE 754 value (numbers between -(253 -1) and 253 -1)


          1. Ernest88
            26.01.2018 14:43

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

            var layer_int;
            function layer_anim() {
            document.getElementById('layer1').style.opacity -= 0.01;
            document.getElementById('layer2').style.opacity += 0.01;
            layer_int = setTimeout('layer_anim()', 20);
            }

            Работает он так, что первый слой пропадает, значение падает с 1, а второй слой появляется, с нуля лишь один раз, и остается на уровне 0.01. Как такое может быть, что это за выборочное исполнение команд? Удалось решить как-то, путем замены += на более развернутую форму. Ну что это как не женский каприз (надеюсь никого не обидеть)?


            1. faiwer
              26.01.2018 14:59

              document.getElementById('layer1').style.opacity
              // === ''

              Пустая строка. Неопределённое значение. Так? Складывать строки с числами не самая умная затея. Ок, допустим вы туда принудительно вбили '1'.


              document.getElementById('layer1').style.opacity += 0.01
              "0.990.01"

              Сложили сладкое с горячим — получили ерунду.


              JavaScript это язык со слабой динамической типизацией. Это говорит о том, что типы приводятся к друг другу в ряде случаев. В том числе и строки с числами. Отняв от строки '1' число 0.01 вы получите 0.99. А вот добавив — ту пургу выше. Нравится это или нет, но это так. Подобным образом работают все языки со слабой динамической типизацией. Но, скажем, в PHP для "складывания строк" (конкатенации) предусмотрен оператор .
              А в JS же используется +. Отсюда и ваши проблемы.


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

              Видимо такие знатаки были.


              1. Ernest88
                26.01.2018 15:11

                Благодарю ) Поумнел. Видимо тогда казалось очевидным, что раз от 0 до 1, то должно быть число.


                1. faiwer
                  26.01.2018 15:13
                  +1

                  Все CSS-поля строки. А style в DOM API это проекция на язык CSS из JS-окружения.


                  1. Ernest88
                    26.01.2018 15:17

                    Поставил бы вам «лайк» если бы мог ) Спасибо


            1. mayorovp
              26.01.2018 15:01

              Дык style.opacity — это ж строка...


            1. khim
              26.01.2018 16:16

              Ну что это как не женский каприз (надеюсь никого не обидеть)?
              Это слабая типизация. Вообще все языки делятся на два класса:
              — Ну что ж он такой тупой? (языки с сильной типизацией)
              — Ну что это за женские капризы? (языки со слабой типизацией)
              На самом деле удобнее и проще работать с первым типом, но новички неизменно выбирают второй. Просто потому что это у человека с опытом реакция на всякие сообдения типа cannot concatenate 'str' and 'int' objects это «ну что ж он такой тупой», а новичка реакция «и что ж мне теперь с этим делать?»

              А что «женские капризы» появляются… ну так перепишем += на более развёрнутую форму — авось пропадут. Думать и разбираться для того, чтобы что-то кое-как работающее сотворить не требуется.

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

              Так что языки «с женской логикой» никуда не денутся пока количество программистов растёт в геометрической прогрессии…


  1. Nikita_ab
    26.01.2018 12:05
    -1

    Скрытый текст
    image


  1. yous
    26.01.2018 12:05
    -1

    this is js magic…


  1. barbanel
    26.01.2018 12:33
    -1

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

    старый баян
    <******> к вопросу о вчерашних скриптостраданиях. Только что кодер знакомый прислал, нашёл в коде программы, написанной уволенным коллегой незадолго до ухода:
    <******> #define TRUE FALSE //счастливой отладки суки
    * ****** такого извращённого юмора ещё не встречал


  1. Scf
    26.01.2018 13:43

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


    Сишникам такое неопределенное поведение и не снилось.


    1. Big_Shark
      26.01.2018 13:59

      Для того чтобы этого не происходило достаточно делать строгое сравнение с учетом типа.


    1. mayorovp
      26.01.2018 14:05
      +1

      Э, нет. С точки зрения сишников, это всего лишь unspecified behavior, а не undefined. И концы в этом каскаде ошибок при некоторой сноровке найти можно.

      Undefined Behavior — намного хуже, поскольку с момента UB программа перестает поддаваться логике: у условных операторов может быть исполнено обе ветви или ни одной, бесконечный цикл завершается не начавшись и т.п.


  1. ATLAS1
    26.01.2018 14:22

    В PHP тоже можно:

    class A {
        private $val = 0;
    
        public function __toString()
        {
            $val = $this->val++;
            return (string) $val;
        }
    }
    
    $a = new A();
    
    if ($a == '0' && $a == '1' && $a == '2') {
        echo 'Its true!';
    }


    1. TimsTims
      26.01.2018 15:54

      Имхо проще $a присвоить функцию, и при каждом вызове функция будет возвращать числа. И не надо в таком случае закатывать 1 в кавычки.


      1. ATLAS1
        26.01.2018 15:59

        Не совсем понял. Как вызывать функцию без ()? И как в функции сохранять предыдущее значение?