Чи має сенс використання == у 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) з моделями id (які зазвичай є int).
Scottie

12
@DevSolar Веб-розробка має сенс, коли вам не потрібно мати справу з створенням нативного додатка для кожної з 15 платформ, а також сертифікацією на монопольний App Store кожної платформи.
Damian Yerrick

Відповіді:


232

Я збираюся зробити аргумент за ==

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

JavaScript - це мова, що вводиться в поведінку *. Речі трактуються виходячи з того, що вони можуть зробити, а не їх фактичного типу. Ось чому ви можете викликати метод масиву .mapв NodeList або на наборі вибору jQuery. Це також, чому ви можете зробити 3 - "5"і отримати щось значиме назад - адже "5" може діяти як число.

Виконуючи ==рівність, ви порівнюєте вміст змінної, а не її тип . Ось кілька випадків, коли це корисно:

  • Читання номера від користувача - читання .valueелемента введення в DOM? Нема проблем! Вам не доведеться починати його кидати або турбуватися про його тип - ви можете ==одразу перенести їх на номери та отримати щось значуще.
  • Потрібно перевірити "існування" заявленої змінної? - Ви можете == nullце зробити, оскільки поведінково nullпредставляє, що там нічого немає, і невизначений не має нічого там.
  • Потрібно перевірити, чи отримали ви змістовні дані від користувача? - перевірте, чи введений ==аргумент помилковий , він буде обробляти випадки, коли користувач нічого не вводив, або просто пробіл, який, мабуть, вам потрібен.

Давайте розглянемо приклади Крокфорда та пояснимо їх поведінково:

'' == '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 особливо корисні.
Кріс Сірефіс

224
Проблема ==не в тому, що жодне його порівняння не корисне , це те, що правила неможливо запам’ятати, тому ви майже гарантовано помиляєтесь. Наприклад: "Вам потрібно перевірити, чи отримали ви змістовні дані від користувача?", Але "0" - це змістовне введення та '0'==falseчи правда. Якби ви використовували, ===вам довелося б чітко подумати над цим, і ви не помилилися б.
Timmmm

44
"правила неможливо запам'ятати" <== це одне, що мене відлякує від того, щоб робити що-небудь "значуще" в JavaScript. (і плаваючою математикою, що призводить до проблем з основними
вправами з калькування

9
3 - "5"Приклад піднімає хорошу точку на його власному: навіть якщо використовувати виключно ===для порівняння, що це саме так , як змінні працюють в JavaScript. Неможливо повністю уникнути цього.
Джаретт Міллард

23
@Timmmm note Я тут граю захисника диявола. Я не використовую ==власний код, я вважаю його антидіаграмою, і я повністю згоден з алгоритмом абстрактної рівності важко запам'ятати. Я тут роблю аргументи, які люди висловлюють за це.
Бенджамін Груенбаум

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розріджену карту від ints до значень)
  • значення керування введеннями користувача та форми завжди є рядками (навіть якщо джерело відповідає input[type=number])

З цілого ряду причин було доцільно змусити рядки поводитись як числа, коли це потрібно. І якщо припустити, що порівняння рядків і конкатенація рядків мали різні оператори (наприклад, ::для концентрування та методу порівняння (де ви можете використовувати всі види параметрів щодо чутливості до регістру, а що ні) - це насправді буде менше безладу. Але ця перевантаження оператора, ймовірно, насправді звідки походить "Java" в "JavaScript";)


4
Справедливість бути >=не дуже перехідною. В JS цілком можливо, що ні, a > bні a < bні b == a(наприклад NaN:).
Бенджамін Груенбаум

8
@BenjaminGruenbaum: Це як би сказати +, що насправді не є комутативним, тому що NaN + 5 == NaN + 5це не відповідає. Справа в тому, що >=працює зі значеннями число-ish, для яких ==працює послідовно. Не дивно, що "не число" за своєю суттю не число-ish;)
back2dos

4
Так що погана поведінка Росії ==відповідає поганій поведінці >=? Чудово, зараз я б хотів, щоб був >==...
Eldritch Conundrum

2
@EldritchConundrum: Як я намагався пояснити, поведінка >=досить узгоджується з рештою мови / стандартних API. У своїй сукупності JavaScript вдається бути більшою, ніж сума його вигадливих частин. Якщо ви хотіли б >==, ви також хочете суворого +? На практиці багато таких рішень значно полегшують багато речей. Тож я б не поспішав оцінювати їх як бідних.
back2dos

2
@EldritchConundrum: Знову ж таки: оператори зв'язків призначені для порівняння числових значень, де один операнд насправді може бути рядком. Для типів операндів, для яких >=є багатозначним, ==однаково значимий - ось і все. Ніхто не говорить , що ви повинні порівняти [[]]з false. У таких мовах, як C, результатом такого рівня дурниць є невизначена поведінка. Просто поводьтеся з ним так само: не робіть цього. І ти будеш добре. Вам також не потрібно буде пам'ятати будь-які магічні правила. І тоді насправді досить прямо.
back2dos

8

Як професійний математик я бачу в операторі однаковості Явскрипта == (його також називають "абстрактним порівнянням", "вільною рівністю" ) спробою побудувати відношення еквівалентності між сутностями, що включає рефлексивність , симетричність та перехідність . На жаль, два з цих трьох основних властивостей не спрацьовують:

==не є рефлексивним :

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).


3

Сьогодні я зіткнувся з досить корисним додатком. Якщо ви хочете порівняти прокладені числа, як 01і звичайні цілі числа, ==працює добре. Наприклад:

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

Це заощаджує ви видалення 0 і перетворення на ціле число.


4
Я майже впевнений, що «правильний» спосіб зробити це '04'-0 === 4, або, можливо,parseInt('04', 10) === 4
ratbum

Я не знав, що ти можеш це зробити.
Джон Сноу

7
Я отримую це багато.
Джон Сноу

1
@ratbum or+'01' === 1
Eric Lagergren

1
'011' == 011 // falseв не строгому режимі, а SyntaxError у суворому режимі. :)
Брайан S

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обидва вони хибні! Це цілком нормально сказати

if (p.supervisor) { ... }

а потім nullі undefinedспричинить пропуск коду, який обробляє наглядача. Це правильно, тому що ми не знаємо або не маємо керівника. Все добре. Але дві ситуації не рівні . Ось чому ==неправильно. Знову ж таки, речі можуть бути хибними та використовуватись у сенсі набору качок, що чудово підходить для динамічних мов. Це належний JavaScript, Pythonic, Rubyish тощо. Але знову ж таки, ці речі НЕ рівні.

І не зрозумійте мене почалася не-транзитивності: "0x16" == 10, 10 == "10"але не "10" == "0x16". Так, JavaScript є слабким типом. Так, це примусово. Але примус ніколи і ніколи не повинен застосовуватися до рівності.

До речі, у Крокфорда справді є думки. Але ти знаєш що? Тут він правильний!

FWIW Я розумію, що є, і я особисто стикався з ситуаціями, коли ==зручно! Як би взяти рядковий ввід для чисел і, скажімо, порівняти з 0. Однак це хак. Ви маєте зручність як компроміс за неточну модель світу.

TL; DR: помилковість - це чудова концепція. Вона не повинна поширюватися на рівність.


Дякую, що показали різні ситуації :) Однак ви пропускаєте p5... та ситуація, typeof(p5.supervisor) === typeof(undefined)коли супервізор навіть не існує як концепція: D
TheCatWhisperer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.