Имеет ли смысл использовать == в JavaScript когда-либо?

276

В хороших частях JavaScript Дуглас Крокфорд писал:

В JavaScript есть два набора операторов равенства: ===и !==, и их злые близнецы ==и !=. Хорошие работают так, как вы ожидаете. Если два операнда имеют одинаковый тип и имеют одинаковое значение, то ===производит trueи !==производит false. Злые близнецы поступают правильно, когда операнды относятся к одному и тому же типу, но если они относятся к разным типам, они пытаются привести значения. Правила, по которым они это делают, сложны и не запоминаются. Вот некоторые из интересных случаев:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

Отсутствие транзитивности вызывает тревогу. Мой совет - никогда не использовать злых близнецов. Вместо этого всегда используйте ===и !==. Все приведенные сравнения производятся falseс ===оператором.

Учитывая это однозначное наблюдение, было ли когда-нибудь время, когда использование ==могло бы быть действительно уместным?

Роберт Харви
источник
11
Это имеет смысл во многих местах. Каждый раз, когда становится очевидно, что вы сравниваете две вещи одного и того же типа (это часто случается в моем опыте), == vs === сводится к предпочтениям. В других случаях вам действительно нужно абстрактное сравнение (например, случай, который вы упомянули в своем ответе). Уместно ли это, зависит от соглашений для любого данного проекта.
Привет
4
Что касается "большого количества мест", по моему опыту, случаи, когда это не имеет значения, превосходят число случаев, когда это имеет значение. Ваш опыт может быть другим; Может быть, у нас есть опыт работы с различными проектами. Когда я смотрю на проекты, которые используют ==по умолчанию, ===выделяется и дает мне знать, что происходит что-то важное.
Привет
4
Я не думаю, что JavaScript заходит достаточно далеко, с приведением типов. Он должен иметь еще больше опций приведения типов, как и язык BS .
Марк Бут
5
Одно из мест, где я использую ==, - это сравнение выпадающих идентификаторов (которые всегда char) с идентификаторами моделей (обычно int).
Скотти
12
@DevSolar Веб-разработка имеет смысл, когда вам не нужно иметь дело с производством собственного приложения для каждой из 15 платформ, а также с сертификацией в монопольном App Store каждой платформы, которая имеет такую.
Дамиан Йеррик

Ответы:

232

Я собираюсь привести аргумент в пользу ==

Дуглас Крокфорд, которого вы цитировали, известен своими многочисленными и часто очень полезными мнениями. Хотя в данном конкретном случае я с Крокфордом, стоит упомянуть, что это не единственное мнение. Есть и другие, такие как создатель языка Брендан Айх, которые не видят большой проблемы с этим ==. Аргумент выглядит примерно так:

JavaScript является поведенчески * типизированным языком. Вещи рассматриваются на основе того, что они могут сделать, а не их фактический тип. Вот почему вы можете вызывать .mapметод массива в NodeList или в наборе выбора jQuery. Это также, почему вы можете сделать 3 - "5"и получить что-то значимое обратно - потому что «5» может действовать как число.

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

  • Чтение числа от пользователя - читать .valueэлемент ввода в DOM? Нет проблем! Вам не нужно начинать разыгрывать его или беспокоиться о его типе - вы можете ==сразу получить цифры и получить что-то значимое обратно.
  • Нужно проверить «существование» объявленной переменной? - вы можете сделать == nullэто, поскольку поведенчески nullпредставляете, что там ничего нет, а у undefined там тоже ничего нет.
  • Нужно проверить, получили ли вы значимый вклад от пользователя? - проверьте, является ли ввод ложным с ==аргументом, он будет обрабатывать случаи, когда пользователь ничего не вводил или просто пробел для вас, что, вероятно, то, что вам нужно.

Давайте посмотрим на примеры Крокфорда и объясним их поведение:

'' == '0'           // got input from user vs. didn't get input - so false
0 == ''             // number representing empty and string representing empty - so true
0 == '0'            // these both behave as the number 0 when added to numbers - so true    
false == 'false'    // false vs got input from user which is truthy - so false
false == '0'        // both can substitute for 0 as numbers - so again true

false == undefined  // having nothing is not the same as having a false value - so false
false == null       // having empty is not the same as having a false value - so false
null == undefined   // both don't represent a value - so true

' \t\r\n ' == 0     // didn't get meaningful input from user vs falsey number - true 

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

* некоторые могут предпочесть структурную типизацию имени, которая более распространена, но есть разница - не очень интересно обсуждать эту разницу здесь.

Бенджамин Грюнбаум
источник
8
Это отличный ответ, и я использую все три варианта использования for ==. # 1 и # 3 особенно полезны.
Крис Cirefice
224
Проблема ==не в том, что ни одно из его сравнений не является полезным , а в том, что правила невозможно запомнить, поэтому вы почти гарантированно допустите ошибки. Например: «Нужно проверить, получили ли вы значимый ввод от пользователя?», Но «0» является значимым вводом и '0'==falseявляется истинным. Если бы вы использовали, ===вам пришлось бы об этом подумать, и вы бы не ошиблись.
Тимммм
44
«правила невозможно запомнить» <== это одна вещь, которая пугает меня от выполнения чего-либо «значимого» в Javascript. (и плавающая математика, которая приводит к проблемам в базовых упражнениях по
кальку
9
3 - "5"Пример поднимает хорошую точку на его собственном: даже если использовать исключительно ===для сравнения, что это именно так , как переменные работают в JavaScript. Нет никакого способа избежать этого полностью.
Джаретт Миллард
23
@Timmmm примечание, я играю адвоката дьявола здесь. Я не использую ==в своем собственном коде, я нахожу это анти-паттерном и полностью согласен с тем, что алгоритм абстрактного равенства трудно запомнить. То, что я делаю здесь, - это аргумент, который люди приводят за это
Бенджамин Грюнбаум
95

Оказывается, что jQuery использует конструкцию

if (someObj == null) {
  // do something
}

широко, как сокращение для эквивалентного кода:

if ((someObj === undefined) || (someObj === null))  {
  // do something
}

Это является следствием спецификации языка ECMAScript § 11.9.3, Алгоритм сравнения абстрактного равенства , который, среди прочего, гласит, что

1.  If Type(x) is the same as Type(y), then  
    a.  If Type(x) is Undefined, return true.  
    b.  If Type(x) is Null, return true.

а также

2.  If x is null and y is undefined, return true.
3.  If x is undefined and y is null, return true.

Эта конкретная техника достаточно распространена, и JSHint имеет специально разработанный для нее флаг .

Роберт Харви
источник
10
Нечестно отвечая на твой собственный вопрос. Я хотел бы ответить на это :) == null or undefined- это единственное место, где я не пользуюсь ===или!==
pllee
26
Честно говоря, jQuery вряд ли является моделью кода. Прочитав исходный код jQuery несколько раз, это одна из моих наименее любимых кодовых баз с множеством вложенных троих, непонятных битов, вложений и вещей, которых я бы иначе избегал в реальном коде. Не верьте моему слову, прочитайте его github.com/jquery/jquery/tree/master/src, а затем сравните его с Zepto, который является клоном jQuery: github.com/madrobby/zepto/tree/master/src
Бенджамин Грюнбаум
4
Также обратите внимание, что Zepto по умолчанию используется ==и используется только ===в тех случаях, когда это необходимо: github.com/madrobby/zepto/blob/master/src/event.js
Привет,
2
@ Эй, если быть честным, Zepto тоже вряд ли является модельной кодовой базой - она ​​печально известна тем, что использует __proto__и почти в одиночку вводит ее в спецификацию языка, чтобы избежать взлома мобильных веб-сайтов.
Бенджамин Грюнбаум
2
@BenjaminGruenbaum, который не судил о качестве их кодовой базы, просто указал, что разные проекты следуют различным соглашениям.
Привет
15

Проверка значений для nullили undefinedэто одна вещь, как было обильно объяснено.

Есть еще одна вещь, где ==светит:

Вы можете определить сравнение >=примерно так (люди обычно начинают с этого, >но я считаю это более элегантным):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < bи a <= bоставлены в качестве упражнения для читателя.

Как известно, в JavaScript "3" >= 3и "3" <= 3откуда вы получаете 3 == "3". Вы можете заметить, что это ужасная идея - разрешать сравнение строк и чисел, анализируя строку. Но учитывая , что это так , как это работает, ==абсолютно правильный путь для реализации этого оператора отношения.

Так что действительно хорошо то, ==что это согласуется со всеми другими отношениями. Другими словами, если вы напишите это:

function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  return 0;
}

Вы уже неявно используете ==.

Теперь к довольно связанному вопросу: был ли плохой выбор осуществлять сравнение чисел и строк так, как оно реализовано? В одиночестве это выглядит довольно глупо. Но в контексте других частей JavaScript и DOM это относительно прагматично, учитывая, что:

  • атрибуты всегда являются строками
  • ключи всегда являются строками (в том случае, если вы используете Objectразреженную карту от целочисленных значений до значений)
  • пользовательский ввод и значения элемента управления формы всегда являются строками (даже если источник совпадает input[type=number])

По целому ряду причин имеет смысл заставить строки вести себя как числа, когда это необходимо. И если предположить, что сравнение строк и конкатенация строк имеют разные операторы (например, ::для конкатенации и метод сравнения (где вы можете использовать все виды параметров, касающихся чувствительности к регистру, а что нет)), на самом деле это будет меньше беспорядка. Но эта перегрузка оператора, вероятно, на самом деле происходит от "Java" в "JavaScript";)

back2dos
источник
4
Честно говоря, >=это не совсем переходный процесс. В JS вполне возможно, что ни, a > bни, a < bни b == a(например:) NaN.
Бенджамин Грюнбаум
8
@BenjaminGruenbaum: Это все равно что сказать, +что на самом деле не коммутативно, потому что NaN + 5 == NaN + 5не имеет места. Дело в том, что >=работает со значениями нумерации, для которых ==работает согласованно. Не должно быть удивительно, что "не число" по своей природе не число - число;)
back2dos
4
Таким образом, плохое поведение ==соответствует плохому поведению >=? Отлично, теперь я хотел бы, чтобы >==...
Eldritch Conundrum
2
@EldritchConundrum: Как я пытался объяснить, поведение >=довольно согласуется с остальными языковыми / стандартными API. В целом, JavaScript умудряется быть больше, чем сумма его причудливых частей. Если вы хотите >==, вы также хотите строгий +? На практике многие из этих решений значительно облегчают жизнь. Так что я бы не поспешил судить их как бедных.
back2dos
2
@EldritchConundrum: Опять же: операторы отношений предназначены для сравнения числовых значений, где один операнд может фактически быть строкой. Для типов операндов, для которых >=имеет значение, ==одинаково значимым - и все. Никто не говорит, что вы должны сравнить [[]]с false. В таких языках, как C, результатом такого уровня глупости является неопределенное поведение. Просто относитесь к этому так же: не делайте этого. И у тебя все будет хорошо. Вам также не нужно будет помнить какие-либо магические правила. И тогда это на самом деле довольно прямо вперед.
back2dos
8

Как профессиональный математик, я вижу в операторе одинаковости Javscript == (также называемом «абстрактное сравнение», «слабое равенство» ) попытку построить отношение эквивалентности между сущностями, которое включает в себя рефлексивность , симметрию и переходность . К сожалению, два из этих трех основных свойств не работают:

==не рефлексивно :

A == A может быть ложным, например

NaN == NaN // false

==не является переходным :

A == Bи B == Cвместе не подразумевают A == C, например,

'1' == 1 // true
1 == '01' // true
'1' == '01' // false

Выживает только симметричное свойство:

A == Bподразумевает B == A, что нарушение, вероятно, немыслимо в любом случае и приведет к серьезному восстанию;)

Почему отношения эквивалентности имеют значение?

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

Почему такая ужасная идея писать ==для отношения неэквивалентности?

Потому что это нарушает наше знакомство и интуицию, так как буквально любое интересное отношение сходства, равенства, конгруэнтности, изоморфизма, идентичности и т. Д. Является эквивалентностью.

Тип преобразования

Вместо того, чтобы полагаться на интуитивную эквивалентность, JavaScript вводит преобразование типов:

Оператор равенства преобразует операнды, если они не одного типа, затем применяет строгое сравнение.

Но как определяется преобразование типов? Через набор сложных правил с многочисленными исключениями?

Попытка построить отношение эквивалентности

Булевы. Понятно trueи falseне одно и то же и должно быть в разных классах.

Числа. К счастью, равенство чисел уже четко определено, в котором два разных числа никогда не находятся в одном классе эквивалентности. В математике это так. В JavaScript понятие числа несколько искажается из-за присутствия более экзотического -0, Infinityи -Infinity. Наша математическая интуиция подсказывает , что 0и -0должно быть в том же классе (на самом деле -0 === 0является true), в то время как каждый из бесконечностей представляет собой отдельный класс.

Числа и логические значения. Учитывая количество классов, где мы можем поставить логические значения? falseстановится похожим на 0, тогда как trueстановится похожим на 1другое число:

true == 1 // true
true == 2 // false

Есть ли логика здесь , чтобы положить trueвместе с 1? По общему признанию 1различается, но это так -1. Я лично не вижу смысла переходить trueна 1.

И это становится еще хуже:

true + 2 // 3
true - 1 // 0

Так trueдействительно превращается в 1число! Это логично? Это интуитивно понятно? Ответ оставлен как упражнение;)

Но как насчет этого:

1 && true // true
2 && true // true

Единственное логическое xс x && trueсуществом trueэто x = true. Что доказывает, что оба 1и 2(и любое другое число 0) конвертируются в true! То, что он показывает, - это то, что наше обращение не соответствует другому важному свойству - биекции . Это означает, что две разные сущности могут конвертировать в одну и ту же. Что само по себе не должно быть большой проблемой. Большая проблема возникает, когда мы используем это преобразование, чтобы описать отношение «одинаковости» или «свободного равенства» того, что мы хотим назвать. Но ясно одно - оно не будет отношением эквивалентности и не будет интуитивно описываться через классы эквивалентности.

Но можем ли мы сделать лучше?

По крайней мере, математически - определенно да! Простое отношение эквивалентности между булевыми и числами может быть построено только с falseи 0быть в том же классе. Так false == 0что было бы единственным нетривиальным свободным равенством.

А как насчет строк?

Мы можем обрезать строки из пробелов в начале и конце для преобразования в числа, также мы можем игнорировать нули перед:

'   000 ' == 0 // true
'   0010 ' == 10 // true

Таким образом, мы получаем простое правило для строки - обрезаем пробелы и нули впереди. Либо мы получим число, либо пустую строку, и в этом случае мы конвертируем в это число или ноль. Или мы не получаем число, в этом случае мы не конвертируем и поэтому не получаем новых отношений.

Таким образом, мы могли бы получить идеальное отношение эквивалентности для всего набора логических значений, чисел и строк! Кроме этого ... У разработчиков JavaScript, очевидно, другое мнение:

' ' == '' // false

Таким образом, две строки, в которые оба преобразуются 0, неожиданно не похожи! Почему или почему? Согласно правилу, строки практически равны, когда они строго равны! Как мы видим, не только это правило нарушает транзитивность, но и избыточно! Какой смысл создавать другого оператора, ==чтобы он был строго идентичен другому ===?

Заключение

Оператор свободного равенства ==мог бы быть очень полезным, если бы он соблюдал некоторые фундаментальные математические законы. Но, как это ни печально, его полезность страдает.

Дмитрий Зайцев
источник
Как насчет NaN? Кроме того, если для сравнения со строками не установлен конкретный числовой формат, должно произойти либо неинтуитивное сравнение строк, либо непереходность.
Соломон Уцко
@SolomonUcko NaNдействует как плохой гражданин :-). Транзитивность может и должна поддерживаться для любого сравнения эквивалентности, интуитивно понятного или нет.
Дмитрий Зайцев
7

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

for (var key in obj) {
    var some_number = foo(key, obj[key]);  // or whatever -- this is just an example
    if (key == some_number) {
        blah();
    }
}

Я думаю, что гораздо более естественно выполнять сравнение, key == some_numberа не как Number(key) === some_numberили как key === String(some_number).

Mehrdad
источник
3

Я наткнулся на довольно полезное приложение сегодня. Если вы хотите сравнить дополненные числа, как 01с обычными целыми числами, ==работает просто отлично. Например:

'01' == 1 // true
'02' == 1 // false

Это спасает вас от удаления 0 и преобразования в целое число.

Джон Сноу
источник
4
Я вполне уверен, что «правильный» способ сделать это '04'-0 === 4, или, возможно,parseInt('04', 10) === 4
ratbum
Я не знал, что ты мог сделать это.
Джон Сноу
7
Я это много понимаю.
Джон Сноу
1
@ratbum или+'01' === 1
Эрик Лагергрен
1
'011' == 011 // falseв нестрогом режиме и SyntaxError в строгом режиме. :)
Брайан С
3

Я знаю , что это поздний ответ, но там , кажется, некоторая возможная неразбериха nullи undefined, что ИМХО это то , что делает ==зло, тем более , что отсутствие транзитивности, что достаточно плохо. Рассмотреть возможность:

p1.supervisor = 'Alice';
p2.supervisor = 'None';
p3.supervisor = null;
p4.supervisor = undefined;

Что это значит?

  • p1 есть руководитель, которого зовут "Алиса".
  • p2 имеет руководителя, которого зовут «Нет».
  • p3явно, однозначно, не имеет руководителя .
  • p4может или может иметь руководителя. Мы не знаем, нам все равно, мы не должны знать (проблема конфиденциальности?), Поскольку это не наше дело.

Когда вы используете, ==вы смешиваете, nullи undefinedэто совершенно неправильно. Два термина означают совершенно разные вещи! Сказать, что у меня нет руководителя, просто потому, что я отказался сказать вам, кто мой руководитель, и это неправильно!

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

Теперь я , кстати , не имеют никаких проблем с nullи undefinedоба они falsy! Это совершенно нормально сказать

if (p.supervisor) { ... }

а затем nullи undefinedприведет к пропуску кода, который обрабатывает супервизор. Это правильно, потому что мы не знаем или не имеем руководителя. Все хорошо. Но две ситуации не равны . Вот почему ==это неправильно. Опять же, вещи могут быть ложными и использоваться в смысле утки, что отлично подходит для динамических языков. Это правильный JavaScript, Pythonic, Rubyish и т. Д. Но опять же, эти вещи НЕ равны.

И не заставляйте меня начинать с нетранзитивности "0x16" == 10, 10 == "10"но нет "10" == "0x16". Да, JavaScript слабо набирает. Да, это принудительно. Но принуждение никогда не должно относиться к равенству.

Кстати, у Крокфорда действительно сильные мнения. Но вы знаете, что? Он здесь прав!

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

TL; DR: ложь - отличная концепция. Это не должно распространяться на равенство.

Рэй Тоал
источник
Спасибо за показ различных ситуаций :) Однако вы упускаете p5... единственную ситуацию, typeof(p5.supervisor) === typeof(undefined)когда супервизор даже не существует в качестве концепции: D
TheCatWhisperer