JavaScript

индекс
246,38

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

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

Так что будьте осторожны при сравнивании двух переменных!
+52
25 июня 2009, 12:09
26

комментарии (52)

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

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

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

String b = a;
0
n43jl #
При String b = a; // результат тоже будет world
0
n43jl #
Не совсем понимаю, что вы хотите показать в 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
khizhaster #
Можно проще. Во втором случае сравнение двух объектов между собой.
+1
firstbyte #
Разные экземпляры класса всегда будут отличаться при таком сравнении. Не копал глубоко в Javascript, но тут явно нужно сравнивать значение toString()
0
Cancel #
Угу, но если значение приезжает из функции, например, из какого-то чужого модуля, можно серьёзно наколоться на сравнении.
+1
firstbyte #
Нужно просто к этому привыкнуть, в любом языке есть парочка «подводных камней», которые со временем могут оказаться даже полезными.
+3
nerezus #
> С точки зрения программиста примитивное значение типа string и объект, созданный из строки конструктором String(), практически неотличимы

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

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

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

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

Я вот тоже задался вопросом почему тут habrahabr.ru/blogs/webdev/62556/ и меня тут же заминусовали. Что делать, леммингов тут значительно больше.
+3
nerezus #
Программиисты? Тогда я врач, т.к. знаю, как пластырь лепить. И каскадер, т.к. кувыркаться умею.
+1
cherepaha #
Ну вас-то на работу ни в больницу, ни в кино не возьмут, а тех ребят в ЛамерСофт возьмут. Потому они и имеют формальное право называться программистами.
+4
trg #
В данном примере не совсем очевидно где кончается «примитиный» тип значений и где начинается ссыслчный.
В 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
Cancel #
Двумя понятными словами это очень сложно сказать. Определения из ECMA-262 весьма точны, но для быстрого понимания не очень годятся.

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

jQuery(['1',2]).each(function(){ alert( typeof this ) })
0
eyeofhell #
Это все потому, что есть целых два оператора сравнения — «value equality operator» и «object equality operator». Если это написать большим красным маркером на стене, то все сразу становится хорошо и удобно :).
0
tenshi #
нет, не поэтому.
0
jandosul #
console.log(b.valueOf()==c.valueOf()); // true
0
frostbite #
Вот можно писать obj1 === obj2, например
0
Cancel #
Тогда уж obj1.valueOf() === obj2.valueOf()
0
khizhaster #
А сами то пробовали? ;) Вот вам лог небольшой:
>>> a = 'str';
«str»
>>> b = new String(a)
str
>>> c = new String(a)
str
>>> b === c
false
НЛО прилетело и опубликовало эту надпись здесь
0
Cancel #
Нет, да и, наверное, в нём нет особого смысл. При желании, впрочем, можно легко реализовать и прицепить на вершину прототипной иерархии.
0
borisko #
Перегрузка стандартных операторов рулит :)
+5
dmrt #
Яваскриптовый программер всегда на чеку:
(большая картинка)
www.dvoryan.com/wp-content/uploads/web-team.jpg
0
za4to #
Javascript программист самый симпатичный :)
0
VtQveant #
На эту тему есть волшебная статья [Henry G. Baker, «Equal Rights for Functional Objects or, The More Things Change The More They Are The Same», 1993].
0
sigizmund #
Капитан Очевидность промолчит про метод equals, который есть первый выучиваемый программистами на Джаве (и люто бешено ненавидимый С++-никами которые на ней вынуждены писать).
0
zorro1211 #
Ну тогда ещё «в тему»:
my.prop = null //true
Выражение истинно, если св-во my.prop не существует, либо если оно не существует, но содержит значение null. Т.е. получается что undefined = null //true, хотя они не эквивалентны. Когда их действительно нужно отличать, то следует применять оператор идентичности === или оператор typeOf

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

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

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