25 июня 2009 в 12:09

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

Оператор сравнения (==) в 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.

Так что будьте осторожны при сравнивании двух переменных!
Serge @Cancel
карма
57,2
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (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
      нет, не поэтому.
  • 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
      *typeof
  • 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    
    

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