W JavaScript, Good Parts , Douglas Crockford napisał:
JavaScript ma dwa zestawy operatorów równości:
===
i!==
, i ich złych bliźniaków==
i!=
. Te dobre działają w oczekiwany sposób. Jeśli dwa operandy są tego samego typu i mają tę samą wartość, wówczas===
produkujetrue
i!==
produkujefalse
. Źli bliźniacy postępują właściwie, gdy operandy są tego samego typu, ale jeśli są innego rodzaju, próbują wymuszać wartości. Zasady, według których to robią, są skomplikowane i niezapomniane. Oto niektóre z interesujących przypadków:'' == '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
Brak przechodniości jest niepokojący. Moja rada to nigdy nie używać złych bliźniaków. Zamiast tego zawsze używaj
===
i!==
. Wszystkie przedstawione porównania porównują wynikifalse
z===
operatorem.
Biorąc pod uwagę tę jednoznaczną obserwację, czy kiedykolwiek zdarza się, że stosowanie ==
może być właściwe?
źródło
==
domyślnie,===
wyróżnia się i informuje, że dzieje się coś ważnego.Odpowiedzi:
Będę argumentować za
==
Cytowany przez ciebie Douglas Crockford znany jest z wielu i często bardzo przydatnych opinii. Chociaż w tym konkretnym przypadku jestem z Crockfordem, warto wspomnieć, że nie jest to jedyna opinia. Są tacy jak twórca języków Brendan Eich, którzy nie widzą dużego problemu
==
. Argument wygląda trochę następująco:JavaScript jest językiem pisanym behawioralnie *. Rzeczy są traktowane na podstawie tego, co mogą zrobić, a nie na ich rzeczywistym typie. Dlatego możesz wywołać metodę tablicy
.map
na NodeList lub na zestawie selekcji jQuery. To także dlatego możesz zrobić3 - "5"
i odzyskać coś znaczącego - ponieważ „5” może zachowywać się jak liczba.Kiedy zachowujesz
==
równość, porównujesz zawartość zmiennej, a nie jej typ . Oto kilka przypadków, w których jest to przydatne:.value
element wejściowy w DOM? Nie ma problemu! Nie musisz rzucać go ani martwić się o jego typ - możesz==
od razu zacząć od liczb i odzyskać coś znaczącego.== null
zrobić, ponieważ behawioralnienull
oznacza, że nic tam nie ma, a niezdefiniowane też tam nie ma.==
argument jest fałszywy , potraktuje przypadki, w których użytkownik nic nie wprowadził lub po prostu spacje dla Ciebie, co prawdopodobnie jest tym, czego potrzebujesz.Spójrzmy na przykłady Crockforda i wyjaśnijmy je behawioralnie:
Zasadniczo
==
jest zaprojektowany do pracy w oparciu o zachowanie prymitywów w JavaScript, a nie o to, czym są . Chociaż osobiście nie zgadzam się z tym punktem widzenia, zdecydowanie warto to zrobić - zwłaszcza jeśli weźmiesz ten paradygmat traktowania typów opartych na zachowaniu w całym języku.* niektórzy wolą nazwę typowania strukturalnego, która jest bardziej powszechna, ale istnieje różnica - tak naprawdę nie interesuje jej tutaj omawianie różnic.
źródło
==
nie polega na tym, że żadne z jego porównań nie jest użyteczne , ale na tym, że reguł nie da się zapamiętać, więc masz prawie gwarancję popełnienia błędów. Na przykład: „Chcesz sprawdzić, czy użytkownik dostarczył znaczące dane wejściowe od użytkownika?”, Ale „0” jest znaczącym wejściem i'0'==false
jest prawdziwe. Gdybyś skorzystał===
, musiałbyś wyraźnie o tym pomyśleć i nie popełniłbyś błędu.3 - "5"
przykład sam w sobie podnosi dobrą rację: nawet jeśli używasz wyłącznie===
do porównania, tak właśnie działają zmienne w JavaScript. Nie ma sposobu, aby całkowicie go uciec.==
w swoim własnym kodzie, uważam go za anty-wzór i całkowicie zgadzam się, że algorytm abstrakcyjnej równości jest trudny do zapamiętania. To, co tu robię, polega na argumentowaniu, że ludzie to robią.Okazuje się, że jQuery używa konstrukcji
obszernie, jako skrót dla równoważnego kodu:
Jest to konsekwencja specyfikacji języka skryptowego ECMAS § 11.9.3, Algorytm porównania równości abstrakcyjnej , który stwierdza między innymi, że
i
Ta szczególna technika jest na tyle powszechna, że JSHint ma specjalnie do niej zaprojektowaną flagę .
źródło
== null or undefined
to jedyne miejsce, w którym nie korzystam===
lub!==
==
używać tylko wtedy,===
gdy jest to potrzebne: github.com/madrobby/zepto/blob/master/src/event.js__proto__
i z kolei zmuszania go prawie do samodzielnego wprowadzania specyfikacji języka, aby uniknąć uszkodzenia stron mobilnych.Sprawdzanie wartości dla
null
lubundefined
jest jedną rzeczą, jak zostało to obficie wyjaśnione.Jest jeszcze jedna rzecz, w której
==
błyszczy:Możesz zdefiniować porównanie w ten
>=
sposób (ludzie zwykle zaczynają od,>
ale uważam to za bardziej eleganckie):a > b
<=>a >= b && !(b >= a)
a == b
<=>a >= b && b >= a
a < b
ia <= b
są pozostawione czytelnikowi jako ćwiczenie.Jak wiemy, w JavaScript
"3" >= 3
i"3" <= 3
, z którego otrzymujesz3 == "3"
. Możesz stwierdzić, że strasznym pomysłem jest umożliwienie implementacji porównywania ciągów z liczbami przez ich parsowanie. Ale biorąc pod uwagę, że jest to sposób to działa,==
jest absolutnie poprawny sposób realizacji tego operatora relacji.Naprawdę dobrą rzeczą
==
jest to, że jest spójna ze wszystkimi innymi związkami. Innymi słowy, jeśli napiszesz to:Używasz domyślnie
==
już.Teraz do dość pokrewnego pytania: czy sposób implementacji porównania liczb i ciągów był zły? Z perspektywy czasu wydaje się to dość głupie. Ale w kontekście innych części JavaScript i DOM jest to stosunkowo pragmatyczne, biorąc pod uwagę, że:
Object
aby uzyskać rzadką mapę od wartości int do wartości)input[type=number]
)Z wielu powodów sensowne było, aby w razie potrzeby łańcuchy zachowywały się jak liczby. Zakładając, że porównywanie i konkatenacja łańcuchów zawierały różne operatory (np.
::
Do konkatowania i metodę porównywania (gdzie można użyć wszelkiego rodzaju parametrów dotyczących rozróżniania wielkości liter i co nie)), w rzeczywistości byłby to mniejszy bałagan. Ale przeciążenie tego operatora jest prawdopodobnie w rzeczywistości tym, skąd pochodzi „Java” w „JavaScript”;)źródło
>=
nie jest tak naprawdę przechodnia. W JS jest całkiem możliwe, że ania > b
ania < b
anib == a
(na przykładNaN
:).+
nie jest tak naprawdę przemienny, ponieważNaN + 5 == NaN + 5
nie ma znaczenia. Chodzi o to, że>=
działa z wartościami liczbowymi, dla których==
działa konsekwentnie. Nie powinno dziwić, że „nie liczba” z natury nie jest liczbą;)==
jest zgodne ze złym zachowaniem>=
? Świetnie, teraz żałuję, że nie było>==
...>=
jest raczej spójne z resztą interfejsu API języka / standardu. W sumie JavaScript jest czymś więcej niż tylko sumą dziwacznych części. Jeśli chcesz>==
, czy chciałbyś również surowego+
? W praktyce wiele z tych decyzji znacznie ułatwia wiele rzeczy. Więc nie spieszyłbym się z osądzaniem ich jako biednych.>=
znaczenie==
jest równie ważne, to wszystko - to wszystko. Nikt nie mówi, należy porównać[[]]
zfalse
. W językach takich jak C wynikiem tego poziomu bzdur jest niezdefiniowane zachowanie. Po prostu traktuj to w ten sam sposób: nie rób tego. I wszystko będzie dobrze. Nie musisz też pamiętać żadnych magicznych zasad. A potem jest to raczej proste.Jako profesjonalny matematyk widzę w operatorze podobieństwa Javscript
==
( zwanym także „porównaniem abstrakcyjnym”, „luźną równość” ) próbę zbudowania relacji równoważności między jednostkami, która obejmuje bycie zwrotnym , symetrycznym i przechodnim . Niestety dwie z tych trzech podstawowych właściwości zawodzą:==
nie jest zwrotny :A == A
może być fałszywe, np==
nie jest przechodnie :A == B
iB == C
razem nie oznaczająA == C
, npPrzetrwa tylko własność symetryczna :
A == B
sugerujeB == A
, które naruszenie jest w każdym razie nie do pomyślenia i doprowadziłoby do poważnego buntu;)Dlaczego stosunki równoważności mają znaczenie?
Ponieważ jest to najważniejszy i najczęstszy rodzaj relacji, poparty licznymi przykładami i aplikacjami. Najważniejszą aplikacją jest rozkład jednostek na klasy równoważności , co samo w sobie jest bardzo wygodnym i intuicyjnym sposobem rozumienia relacji. Brak równoważności prowadzi do braku klas równoważności, co z kolei prowadzi do znanego braku intuicyjności i niepotrzebnej złożoności.
Dlaczego tak okropny jest pomysł na pisanie
==
relacji nierównomierności?Ponieważ niszczy naszą znajomość i intuicję, ponieważ dosłownie każda interesująca relacja podobieństwa, równości, kongruencji, izomorfizmu, tożsamości itp. Jest równoważna.
Konwersja typu
Zamiast polegać na intuicyjnej równoważności, JavaScript wprowadza konwersję typu:
Ale jak definiuje się konwersję typu? Poprzez zestaw skomplikowanych zasad z licznymi wyjątkami?
Próba zbudowania relacji równoważności
Booleany. Oczywiście
true
ifalse
nie są takie same i powinny być w różnych klasach.Liczby. Na szczęście równość liczb jest już dobrze zdefiniowana, w której dwie różne liczby nigdy nie należą do tej samej klasy równoważności. To znaczy w matematyce. W JavaScript pojęcie liczby jest nieco zdeformowane przez obecność bardziej egzotycznych
-0
,Infinity
i-Infinity
. Nasza intuicja matematyczna dyktuje to0
i-0
powinna należeć do tej samej klasy (w rzeczywistości-0 === 0
jesttrue
), podczas gdy każda z nieskończoności jest odrębną klasą.Liczby i liczby booleańskie. Biorąc pod uwagę klasy liczbowe, gdzie umieszczamy booleany?
false
staje się podobny do0
, podczas gdytrue
staje się podobny do,1
ale nie ma innej liczby:Czy jest jakaś logika tutaj umieścić
true
razem z1
? Wprawdzie1
wyróżnia się, ale tak też jest-1
. Ja osobiście nie widzę żadnego powodu, aby przekształcićtrue
się1
.I staje się jeszcze gorzej:
Tak więc
true
rzeczywiście jest przeliczany na1
wszystkie liczby! Czy to logiczne? Czy to jest intuicyjne? Odpowiedź pozostawia się jako ćwiczenie;)Ale co z tym:
Jedyna logiczna
x
zx && true
istotytrue
jestx = true
. Co dowodzi, że zarówno1
i2
(i każda inna liczba niż0
) przekształcają siętrue
! Pokazuje to, że nasza konwersja zawodzi inna ważna właściwość - biject . Oznacza to, że dwa różne podmioty mogą konwertować na ten sam. Co samo w sobie nie musi stanowić dużego problemu. Duży problem powstaje, gdy używamy tej konwersji do opisania relacji „identyczności” lub „luźnej równości” tego, co chcemy to nazwać. Ale jedno jest jasne - nie będzie to relacja równoważności i nie będzie intuicyjnie opisana za pomocą klas równoważności.Ale czy możemy zrobić lepiej?
Przynajmniej matematycznie - zdecydowanie tak! Prosty relacja równoważności między logicznych i numery mogą być zbudowane z tylko
false
i0
będąc w tej samej klasie. Takfalse == 0
byłoby tylko nietrywialne luźne równość.Co z ciągami znaków?
Możemy przycinać ciągi znaków z białych znaków na początku i na końcu, aby konwertować na liczby, a także możemy ignorować zera z przodu:
Otrzymujemy więc prostą regułę dla ciągu - przycinaj białe spacje i zera z przodu. Otrzymujemy liczbę lub pusty ciąg, w którym to przypadku konwertujemy na tę liczbę lub zero. Lub nie otrzymujemy numeru, w którym to przypadku nie dokonujemy konwersji, więc nie otrzymujemy żadnej nowej relacji.
W ten sposób moglibyśmy uzyskać idealną relację równoważności całkowitego zestawu wartości logicznych, liczb i ciągów! Tyle że ... projektanci JavaScript mają oczywiście inną opinię:
Zatem dwa ciągi, na które oba konwertują,
0
nagle stają się niepodobne! Dlaczego lub dlaczego Zgodnie z regułą struny są luźno równe dokładnie wtedy, gdy są ściśle równe! Ta reguła nie tylko łamie przechodniość, jak widzimy, ale także jest zbędna! Po co tworzyć innego operatora,==
aby był ściśle identyczny z drugim===
?Wniosek
Luźny operator równości
==
mógłby być bardzo przydatny, gdyby przestrzegał podstawowych praw matematycznych. Ale jak to niestety nie działa, jego użyteczność cierpi.źródło
NaN
? Ponadto, o ile nie zostanie wymuszony określony format liczb do porównywania z ciągami znaków, musi wystąpić nieintuicyjne porównanie ciągu znaków lub brak przechodniości.NaN
działa jako zły obywatel :-). Przejrzystość może i powinna być utrzymana dla każdego porównania równoważności, intuicyjnego lub nie.Tak, natrafiłem na przypadek użycia dla niego, a mianowicie podczas porównywania klucza z wartością liczbową:
Myślę, że bardziej naturalne jest porównywanie
key == some_number
niż jakoNumber(key) === some_number
lub jakokey === String(some_number)
.źródło
Dzisiaj spotkałem całkiem przydatną aplikację. Jeśli chcesz porównać liczby dopełniane, jak
01
normalne liczby całkowite,==
działa dobrze. Na przykład:Oszczędza to usunięcia 0 i zamiany na liczbę całkowitą.
źródło
'04'-0 === 4
, a możeparseInt('04', 10) === 4
+'01' === 1
'011' == 011 // false
w trybie ścisłym, a SyntaxError w trybie ścisłym. :)Wiem, że to późna odpowiedź, ale wydaje się, że niektóre możliwe zamieszania
null
iundefined
, co IMHO jest to, co czyni==
zło, tym bardziej, że brak przechodniości, który jest na tyle złe. Rozważać:Co to znaczy?
p1
ma przełożonego o imieniu „Alice”.p2
ma przełożonego o imieniu „Brak”.p3
wyraźnie, jednoznacznie, nie ma przełożonego .p4
może lub może mieć przełożonego. Nie wiemy, nie obchodzi nas to, nie powinniśmy wiedzieć (kwestia prywatności?), Ponieważ to nie jest nasza sprawa.Kiedy używasz
==
jesteś utożsamiającnull
iundefined
która jest całkowicie niewłaściwe. Te dwa terminy oznaczają zupełnie inne rzeczy! Mówienie, że nie mam przełożonego, po prostu dlatego, że odmówiłem powiedzenia, kto jest moim przełożonym, jest błędne!Rozumiem, że są programiści, którzy nie dbają o tę różnicę między
null
iundefined
czy zdecydują się używać tych terminów inaczej. A jeśli twój świat nie używanull
iundefined
poprawnie, lub chcesz dać własną interpretację tych warunków, niech tak będzie. Nie sądzę jednak, żeby to był dobry pomysł.A tak przy okazji, nie mam problemu z
null
iundefined
oba są falsy! Można całkowicie powiedzieća potem
null
iundefined
spowodowałoby kod, który przetwarza przełożonego zostać pominięty. To prawda, ponieważ nie wiemy lub nie mamy przełożonego. Wszystko dobrze. Ale dwie sytuacje nie są równe . Właśnie dlatego==
się myli. Znowu rzeczy mogą być fałszywe i używane w znaczeniu pisania kaczego, co jest idealne dla dynamicznych języków. To jest poprawny JavaScript, Python, Rubyish itp. Ale znowu, te rzeczy NIE są równe.I nie zaczynaj mi brak przechodniości:
"0x16" == 10
,10 == "10"
ale nie"10" == "0x16"
. Tak, JavaScript jest słabym typem. Tak, to jest przymus. Ale przymus nigdy nie powinien nigdy odnosić się do równości.Nawiasem mówiąc, Crockford ma mocne opinie. Ale wiesz co? Ma rację tutaj!
FWIW Rozumiem, że istnieją i osobiście wpadłem na sytuacje, w których
==
jest to wygodne! Jak pobieranie ciągów znaków dla liczb i, powiedzmy, porównywanie do 0. Jednak to jest hack. Masz wygodę jako kompromis dla niedokładnego modelu świata.TL; DR: fałsz to świetna koncepcja. Nie powinna rozciągać się na równość.
źródło
p5
...typeof(p5.supervisor) === typeof(undefined)