Подводные камни оператора сравнения

    Оператор сравнения (==) в JavaScript не является транзитивным. Переводя с математического, это означает, что из того, что a==b и a==c не следует, что b==c.

    Простой пример:

    var a = "строка";
    var b = new String(a);
    var c = new String(a);

    alert(a==b); // true
    alert(a==c); // true
    alert(b==c); // false


    В чём же дело? А дело в том, что переменные b и c — это объекты (причём разные), а a — примитивное значение (строковой литерал). Две переменных-объекта считаются неравными, если они ссылаются на разные объекты. При сравнении же примитивного значения и объекта используются другие правила — всё приводится к строкам и затем сравнивается.

    Чем это чревато? Чревато очень трудноуловимыми ошибками. С точки зрения программиста примитивное значение типа string и объект, созданный из строки конструктором String(), практически неотличимы, и даже во многих книгах этот момент упоминается мимоходом, без конкретных примеров.

    Аналогичная ситуация и с другими примитивными типами и соответствующими объектами, например, Number.

    Так что будьте осторожны при сравнивании двух переменных!
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 52
    • 0
      Ну дык во всех интерпретируемых языках со слабой типизацией такие финты ушами могут стоить долгих часов отладки ))
      • 0
        Тут дело не в слабой типизации. Тут дело в референсах/указателям.
      • +3
        b.valueOf() == c.valueOf()
        • +5
          Это не оператор сравнения, это сравнение строкового литерала со строкой и сравнение двух строк между собой. Это не одно и то же.
          • +9
            Это я к тому, что тут надо программисту понимать, как работает яваскрипт, ничего подводного в этом нет. Точно так же нельзя сравнивать в лоб две переменные-массива. Открываем Фленагана и прямо в первых главках это всё отлично описано.

            Тут недавно товарищ для себя и хабра тег legend открывал, эта статья получается откуда-то оттуда же. Грамотный Javascript-программист обязан знать такие вещи.
            • 0
              Проблема в том, что на этом аспекте практически во всех книгах внимание не акцентируется. Про массив всем более-менее всё очевидно, поскольку это всегда объект. Но строки-то бывают не только примитивными типами, но ещё и объектами (хоть и редко). Результатом практически всех встроенных объектов является примитивный тип, поэтому практически всегда никаких проблем не возникает. Но если кто-то напишет функцию, возвращающую именно объект, то проблема становится актуальной.

              А статья как раз таки и адресована не грамотным программистам, а, скорее, начинающим. В javascript создаётся иллюзия понятности, отчасти авторами дурных книг; и человек прочитав всякое, начинает мнить себя спецом. Вот именно этой аудитории и адресован текст.
              • 0
                Хм, наерное, я слишком строго смотрю на вещи. Новичкам это действительно будет полезно, хотя на практике на эти грабли напарываются, наверное, на первом же скрипте, работающим со строками.
                • 0
                  Вот именно на этом точно не напарываются, потому что практическе всегда результаты всех встроенных операций/методов/функций возвращают примитивные типы везде, где это возможно.
                • 0
                  а в java со строками ещё веселее…
                  String a = «world»;
                  String b = «world»;
                  a=«hello»;
                  print(b); // результат «hello»;
                  :)
                  • 0
                    Наверное, имелось в виду

                    String b = a;
                    • 0
                      При String b = a; // результат тоже будет world
                    • 0
                      Не совсем понимаю, что вы хотите показать в Java, но то, что привели вы — неправильно. Результат конечно же будет «world». Объект String в Java immutable (неизменяем), поэтому при изменении a — создаются новые объекты и а начинает указывать на новый объект. String b не изменится и при таком коде String a = «world»; String b = a; a = «hello»;…

                      Как помнится мне, в Java String особенность только в том, что JVM может применять String constant pool. И тогда появляется различие в String a = «a»; и String a = new String(«a»); В первом варианте строковая константа сначала ищется в пуле, если ее там нет, то объект создается и помещается в пул.
                      Поэтому при неправильном сравнении строк (не используя equals) возникает JVM зависимая фича:
                      String a = «a»;
                      String b = «a»; // a == b TRUE
                      String b = new String(«a»); // a == b FALSE
                      String a = new String(«a»); // a == b FALSE
                • 0
                  Можно проще. Во втором случае сравнение двух объектов между собой.
                • +1
                  Разные экземпляры класса всегда будут отличаться при таком сравнении. Не копал глубоко в Javascript, но тут явно нужно сравнивать значение toString()
                  • 0
                    Угу, но если значение приезжает из функции, например, из какого-то чужого модуля, можно серьёзно наколоться на сравнении.
                    • +1
                      Нужно просто к этому привыкнуть, в любом языке есть парочка «подводных камней», которые со временем могут оказаться даже полезными.
                  • +3
                    > С точки зрения программиста примитивное значение типа string и объект, созданный из строки конструктором String(), практически неотличимы

                    С точки зрения КАКОГО програмимиста? Который не умеет отличать ссылочные типы от типов значений?
                    Этот пример разбирается практически в любой книге по программированию.
                    • 0
                      Вы удивитесь, как много javascript-программистов не знают разницы между a=String(«abc») и a=new String(«abc»). А уж о прототипной сущности языка вообще единицы знают :).
                      • +6
                        Почему эти люди называют себя Javascript-программистами?
                        • 0
                          Это терминологический вопрос. Не медаль же давать за знание спеков языка. Лично меня не коробит, пусть как угодно себя называют.
                          • +8
                            Медаль — не медаль, но знать теоретическую сторону технологии — для профессионального владения ею — нужно обязательно.

                            Кстати, попутно спрошу всех. Будет ли интересно разобрать некоторые тонкости ECMA? Почему спрашиваю: я обычно не пишу статьи, но, на форумах, порой, (кто желает углубиться в JS) спрашивают о теоретических тонкостях JS (Variable object, Scope chain, различия Function declaration от Function expression и т.д.). Каждый раз писать одно и то же — тоже не хочется, как правило, кидаются ссылки на предыдущие ответы. Но так получается, вырвано из контекста и далеко не всегда усваивается. Вот я и думаю, собрать на одном блоге (а, может, и здесь) некоторый терминологическо-теоретический справочник по данным аспектам.

                            Какой процент интересуется JS глубоко? Есть ли необходимость в этом?
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • 0
                                мне будет интересно!
                                • 0
                                  Необходимость есть, конечно же пишите.
                                  • 0
                                    Присоединюсь, и могу сказать, что людей которым это было бы полезно на порядки больше тех, кто отметился в этих комментариях.
                                    • 0
                                      Необходимость есть. Будто сам не знаешь. ;)
                                      • 0
                                        Ну, насчет необходимости, я имел в виду больше интерес — интересен ли сам JS в глубоком понимании, или многим достаточно выучить лишь некий фреймворк ;) Zeroglif, если что — поможешь мне поправками и дополнениями :)

                                        Ок, всем спасибо, в скором времени постараюсь выложить некоторый материал из наиболее часто спрашиваемых областей ECMAscript'a. Скорей всего, это будет напоминать справочную информацию (чтобы, в случае последующих аналогичных вопросов, ссылаться на эти описания, а не на отрывки из ответов с форумов); стилистику подачи постараюсь сделать как можно более доступную (не строго-теоретико-сухую, как в стандарте), но, всё-таки, не спускаться до кухонно-разговорного языка, т.к. это, всё же, будут не статьи для новичков, и нужно будет иметь уже хоть какую-нибудь теоретическую базу в JS. Но, конечно, всё можно будет уточнить в комментариях.
                                      • 0
                                        Я интересуюсь. Между тем, кто интересуется, тот и помочь сможет в организации знаний.
                                        • 0
                                          Да, конечно, помощь в знаниях всегда приветствуется.
                                    • 0
                                      Чтобы зарабатывать деньги, продавая своё незнание :)
                                      • 0
                                        Потому что другие называют себя к примеру «верстальщик» или «веб-программист». А сами даже спецификацию толком не читали.

                                        Я вот тоже задался вопросом почему тут habrahabr.ru/blogs/webdev/62556/ и меня тут же заминусовали. Что делать, леммингов тут значительно больше.
                                      • +3
                                        Программиисты? Тогда я врач, т.к. знаю, как пластырь лепить. И каскадер, т.к. кувыркаться умею.
                                        • +1
                                          Ну вас-то на работу ни в больницу, ни в кино не возьмут, а тех ребят в ЛамерСофт возьмут. Потому они и имеют формальное право называться программистами.
                                      • +4
                                        В данном примере не совсем очевидно где кончается «примитиный» тип значений и где начинается ссыслчный.
                                        В Java тоже есть подобные подводные камни:

                                        Integer i1 = 100;
                                        Integer i2 = 100;
                                        Integer i3 = 1000;
                                        Integer i4 = 1000;
                                        System.out.println(i1==i2);
                                        System.out.println(i3==i4);

                                        выведет true false, так как значения [-128;127] сравниваются по значению, остальные — по ссылке.
                                        • 0
                                          Двумя понятными словами это очень сложно сказать. Определения из ECMA-262 весьма точны, но для быстрого понимания не очень годятся.

                                          Примитивный — это, например, значение строкового литерала, или результат выполнения большинства функций, возвращающих строки. Ссылочный тип начинается там, где слово new встречается. Но это так, на пальцах. В общем случае это неверно.
                                          • –1
                                            объектные строки скорее так появляются:

                                            jQuery(['1',2]).each(function(){ alert( typeof this ) })
                                      • 0
                                        Это все потому, что есть целых два оператора сравнения — «value equality operator» и «object equality operator». Если это написать большим красным маркером на стене, то все сразу становится хорошо и удобно :).
                                      • 0
                                        console.log(b.valueOf()==c.valueOf()); // true
                                        • 0
                                          Вот можно писать obj1 === obj2, например
                                          • 0
                                            Тогда уж obj1.valueOf() === obj2.valueOf()
                                          • 0
                                            А сами то пробовали? ;) Вот вам лог небольшой:
                                            >>> a = 'str';
                                            «str»
                                            >>> b = new String(a)
                                            str
                                            >>> c = new String(a)
                                            str
                                            >>> b === c
                                            false
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                              • 0
                                                Нет, да и, наверное, в нём нет особого смысл. При желании, впрочем, можно легко реализовать и прицепить на вершину прототипной иерархии.
                                              • 0
                                                Перегрузка стандартных операторов рулит :)
                                                • +5
                                                  Яваскриптовый программер всегда на чеку:
                                                  (большая картинка)
                                                  www.dvoryan.com/wp-content/uploads/web-team.jpg
                                                  • 0
                                                    Javascript программист самый симпатичный :)
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                    • 0
                                                      Капитан Очевидность промолчит про метод equals, который есть первый выучиваемый программистами на Джаве (и люто бешено ненавидимый С++-никами которые на ней вынуждены писать).
                                                      • 0
                                                        Ну тогда ещё «в тему»:
                                                        my.prop = null //true
                                                        Выражение истинно, если св-во my.prop не существует, либо если оно не существует, но содержит значение null. Т.е. получается что undefined = null //true, хотя они не эквивалентны. Когда их действительно нужно отличать, то следует применять оператор идентичности === или оператор typeOf

                                                        Источник — книга Девида Фленагана "Javascript. Подробное руководство, 5-е издание".
                                                      • 0
                                                        Практически во всех языках можно найти примеры, когда == не транзитивно.
                                                        Например, в C++, C#, Java и многих других:

                                                            int a = 1000000000;
                                                            float b = 1000000000;
                                                            int c = 1000000001;
                                                            print(a == b); // true
                                                            print(b == c); // true
                                                            print(a == c); // false    
                                                        

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