Jakie jest wyjaśnienie tych dziwacznych zachowań JavaScript wymienionych w rozmowie " Wat " dla CodeMash 2012?
The 'wat' rozmowa dla CodeMash 2012 zasadniczo wskazuje na kilka dziwnych dziwactw z Ruby i JavaScript.
Zrobiłem JSFiddle z wyników na http://jsfiddle.net/fe479/9/.
Zachowania specyficzne dla JavaScript (bo nie znam Rubiego) są wymienione poniżej.
Znalazłem w JSFiddle, że niektóre z moich wyników nie odpowiadają tym na filmie, i nie jestem pewien, dlaczego. Jestem jednak ciekaw jak W każdym przypadku obsługa JavaScript działa za kulisami.
Empty Array + Empty Array
[] + []
result:
<Empty String>
Jestem dość ciekawy operatora +
, gdy jest używany z tablicami w JavaScript.
To pasuje do wyniku filmu.
Empty Array + Object
[] + {}
result:
[Object]
To pasuje do wyniku wideo. Co tu się dzieje? Dlaczego jest to obiekt. Co robi operator +
?
Object + Empty Array
{} + []
result:
[Object]
To nie pasuje do nagrania. Film sugeruje, że wynik jest 0, podczas gdy ja dostaję [obiekt].
Object + Object
{} + {}
result:
[Object][Object]
To też nie pasuje do filmu, a jak wyprowadzenie zmiennej powoduje powstanie dwóch obiektów? Może mój JSFiddle się myli.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Robienie wat + 1 daje wat1wat1wat1wat1
...
Podejrzewam, że jest to po prostu proste zachowanie, że próba odjęcia liczby z ciągu powoduje NaN.
5 answers
Oto lista wyjaśnień dla wyników, które widzisz (i które powinieneś widzieć). Odniesienia, których używam, pochodzą ze standardu ECMA-262 .
-
[] + []
W przypadku użycia operatora dodawania, zarówno lewy, jak i prawy operand są najpierw konwertowane na podstawowe(§11.6.1). Jak na §9.1, konwersja obiektu (w tym przypadku tablicy) na prymitywny zwraca jego domyślną wartość, która dla obiektów z poprawną metodą
toString()
jest wynik wywołaniaobject.toString()
(§8.12.8). Dla tablic jest to to samo co wywołaniearray.join()
(§15.4.4.2). Dołączenie pustej tablicy skutkuje pustym łańcuchem, więc krok # 7 operatora dodawania zwraca konkatenację dwóch pustych łańcuchów, które są pustym łańcuchem. -
[] + {}
Podobnie jak
[] + []
, oba operandy są najpierw konwertowane na prymitywy. Dla "Object objects" (§15.2) jest to ponownie wynik wywołaniaobject.toString()
, które dla non-null, Undefined obiekty to"[object Object]"
(§15.2.4.2). -
{} + []
{}
tutaj nie jest przetwarzany jako obiekt, ale jako pusty blok (§12.1, przynajmniej tak długo, jak nie zmuszasz tego stwierdzenia do wyrażenia, ale więcej o tym później). Zwracana wartość pustych bloków jest pusta, więc wynik tego polecenia jest taki sam jak+[]
. Operator unary+
(§11.4.6) zwracaToNumber(ToPrimitive(operand))
. Jak już wiemy, {[13] } jest ciągiem pustym, a według §9.3.1,ToNumber("")
jest 0. -
{} + {}
Podobnie jak w poprzednim przypadku, pierwszy {[9] } jest przetwarzany jako blok z pustą zwracaną wartością. Ponownie,
+{}
jest tym samym coToNumber(ToPrimitive({}))
, aToPrimitive({})
jest"[object Object]"
(Zobacz[] + {}
). Aby uzyskać wynik+{}
, musimy zastosować {[23] } na łańcuchu"[object Object]"
. Wykonując kroki z §9.3.1, otrzymujemyNaN
w wyniku:Jeśli gramatyka nie może zinterpretować ciągu jako rozszerzenia StringNumericLiteral , wtedy wynikiem ToNumber jest NaN .
-
Array(16).join("wat" - 1)
Zgodnie z §15.4.1.1 oraz §15.4.2.2,
Array(16)
tworzy nową tablicę o długości 16. Aby uzyskać wartość argumentu join, §11.6.2 kroki # 5 i # 6 pokazują, że musimy przekonwertować oba operandy na liczbę za pomocąToNumber
.ToNumber(1)
jest po prostu 1 (§9.3), natomiastToNumber("wat")
znowu jest {[25] } Jak na §9.3.1. Po 7. kroku §11.6.2, §11.6.3 dyktuje, żeJeśli któryś z operandów to NaN , wynikiem jest NaN .
Więc argumentem do
Array(16).join
jestNaN
. Po §15.4.4.5 (Array.prototype.join
), musimy wywołaćToString
na argument, który jest"NaN"
(§9.8.1):If M is NaN , return the String
"NaN"
.Po kroku 10§15.4.4.5, otrzymujemy 15 powtórzeń konkatenacji
"NaN"
i pusty ciąg, który jest równy wynikowi, który widzisz. Podczas używania"wat" + 1
zamiast"wat" - 1
jako argumentu, operator dodawania konwertuje1
na ciąg znaków zamiast"wat"
na liczbę, więc skutecznie wywołujeArray(16).join("wat1")
.
Dlaczego widzisz różne wyniki dla przypadku {} + []
: używając go jako argumentu funkcji, wymuszasz wyrażenie ExpressionStatement , co sprawia, że nie można parsować {}
jako pustego bloku, więc zamiast tego jest on parsowany jako pusty obiekt dosłowny.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-18 13:24:32
To jest bardziej komentarz niż odpowiedź, ale z jakiegoś powodu nie mogę skomentować twojego pytania. Chciałem poprawić Twój kod JSFiddle. Jednak opublikowałem to na haker News i ktoś zasugerował, że odświeżam go tutaj.
Problem w kodzie JSFiddle polega na tym, że ({})
(otwieranie nawiasów w nawiasach) nie jest tym samym co {}
(otwieranie nawiasów jako początek linii kodu). Więc kiedy piszesz out({} + [])
zmuszasz {}
do bycia czymś, czym nie jest, kiedy piszesz {} + []
. Jest to część ogólnej " wat " - ness Javascript.
Podstawową ideą było proste JavaScript chciał umożliwić obie te formy:
if (u)
v;
if (x) {
y;
z;
}
W tym celu wykonano dwie interpretacje otwierającej klamry: 1. jest nie jest wymagane i 2. może pojawić się w dowolnym miejscu .
To był zły ruch. Prawdziwy kod nie ma nawiasu otwierającego pojawiającego się pośrodku niczego, a prawdziwy kod również wydaje się być bardziej delikatny, gdy używa pierwszej formy, a nie drugi. (Mniej więcej raz na drugi miesiąc w mojej ostatniej pracy, dostałem wezwanie do biurka współpracownika, gdy ich modyfikacje mojego kodu nie działały, a problem polegał na tym, że dodali linię do "Jeśli" bez dodawania kręconych szelek. W końcu przyzwyczaiłem się, że kręcone aparaty są zawsze wymagane, nawet jeśli piszesz tylko jedną linijkę.)
Na szczęście w wielu przypadkach eval() powieli pełną zawartość JavaScript. Kod JSFiddle powinien brzmieć:
function out(code) {
function format(x) {
return typeof x === "string" ?
JSON.stringify(x) : x;
}
document.writeln('>>> ' + code);
document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");
[także to jest pierwszy raz, kiedy piszę dokument.writeln w wielu wielu wielu lat, i czuję się trochę brudny pisząc wszystko, co dotyczy obu dokumentów.writeln () i eval ().]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-30 18:40:29
Popieram rozwiązanie @ Ventero. Jeśli chcesz, możesz przejść do szczegółów, jak +
konwertuje swoje operandy.
Pierwszy krok (§9.1): konwertuje oba operandy na primitives (primitives values are undefined
, null
, wartości logiczne, liczby, łańcuchy; wszystkie inne wartości są obiektami, w tym tablicami i funkcjami). Jeśli operand jest już prymitywny, jesteś skończony. Jeśli nie, jest to obiekt obj
i wykonywane są następujące kroki:
- Call
obj.valueOf()
. Jeśli zwróci prymityw, jesteś skończony. Bezpośrednie instancjeObject
i tablic zwracają się same, więc to jeszcze nie koniec. - Call
obj.toString()
. Jeśli zwróci prymityw, jesteś skończony.{}
i[]
obie zwracają ciąg znaków, więc gotowe. - w przeciwnym razie rzuć
TypeError
.
Dla dat, Krok 1 i 2 są zamieniane. Można zaobserwować zachowanie konwersji w następujący sposób:
var obj = {
valueOf: function () {
console.log("valueOf");
return {}; // not a primitive
},
toString: function () {
console.log("toString");
return {}; // not a primitive
}
}
Interakcja (Number()
najpierw zamienia się na prymitywną, potem na liczbę):
> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
Drugi etap (§11.6.1): jeśli jeden z operandów jest łańcuchem, drugi operand jest również konwertowany na łańcuch, a wynik jest wytwarzany przez połączenie dwóch łańcuchów. W przeciwnym razie oba operandy są konwertowane na liczby, a wynik jest wytwarzany przez dodanie ich.
Bardziej szczegółowe wyjaśnienie procesu konwersji: "Co to jest {} + {} w JavaScript?"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-30 14:34:11
[[31]}możemy odnieść się do specyfikacji i to jest świetne i najdokładniejsze, ale większość przypadków można również wyjaśnić w bardziej zrozumiały sposób za pomocą następujących stwierdzeń:]}
-
Operatory
+
i-
działają tylko z prymitywnymi wartościami. Dokładniej+
(dodawanie) działa z łańcuchami lub liczbami, a+
(unary) i-
(odejmowanie i unary) działa tylko z liczbami. - wszystkie natywne funkcje lub operatory, które oczekują prymitywnej wartości jako argumentu, najpierw przekonwertuje ten argument na żądany typ prymitywny. Odbywa się to za pomocą
valueOf
lubtoString
, które są dostępne na dowolnym obiekcie. To jest powód, dla którego takie funkcje lub operatory nie rzucają błędów podczas wywoływania na obiektach.
Możemy więc powiedzieć, że:
-
[] + []
jest taka sama jakString([]) + String([])
, która jest taka sama jak'' + ''
. Wspomniałem powyżej, że+
(addition) jest również poprawne dla liczb, ale nie ma poprawnej reprezentacji liczb tablicy w JavaScript, więc dodawanie łańcuchów jest używane zamiast tego. -
[] + {}
jest taki sam jakString([]) + String({})
który jest taki sam jak'' + '[object Object]'
-
{} + []
. Ten zasługuje na więcej wyjaśnień (patrz odpowiedź Ventero). W takim przypadku nawiasy klamrowe są traktowane nie jako obiekt, ale jako pusty blok, więc okazuje się, że jest taki sam jak+[]
. Unary+
działa tylko z liczbami, więc implementacja próbuje uzyskać liczbę z[]
. Najpierw próbujevalueOf
, która w przypadku tablic zwraca ten sam obiekt, a następnie próbuje ostateczności: konwersjatoString
wynik do liczby. Możemy zapisać go jako+Number(String([]))
, który jest taki sam jak+Number('')
, który jest taki sam jak+0
. -
Array(16).join("wat" - 1)
odejmowanie-
działa tylko z liczbami, więc jest takie samo jak:Array(16).join(Number("wat") - 1)
, ponieważ"wat"
nie może być przekonwertowana na poprawną liczbę. OtrzymujemyNaN
, a każda operacja arytmetyczna naNaN
daje wynik zNaN
, więc mamy:Array(16).join(NaN)
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 10:17:20
Aby wzmocnić to, co zostało wcześniej udostępnione.
Przyczyna tego zachowania jest częściowo spowodowana słabym typem JavaScript. Na przykład wyrażenie 1 + "2" jest niejednoznaczne, ponieważ istnieją dwie możliwe interpretacje oparte na typach operandów (int, string) i (int int):
- użytkownik zamierza połączyć dwa ciągi znaków, wynik: "12"
- użytkownik zamierza dodać dwie liczby, wynik: 3
Tak więc przy różnych typach wejść, wyjście możliwości rosną.
Algorytm dodawania
- wymuszanie operandów na prymitywnych wartościach
Podstawowe elementy JavaScript to string, number, null, undefined i boolean (Symbol pojawi się wkrótce w ES6). Każda inna wartość jest obiektem (np. tablice, funkcje i Obiekty). Proces przymusu konwertowania obiektów na prymitywne wartości jest opisany w następujący sposób:
-
Jeśli zwracana jest pierwotna wartość when object.wywołanie valueOf (), a następnie zwrócenie tego wartość, w przeciwnym razie Kontynuuj
Jeśli zwracana jest pierwotna wartość when object.wywołana jest metoda toString (), następnie zwraca tę wartość, w przeciwnym razie continue
-
Throw a TypeError
Uwaga: Dla wartości date należy wywołać ToString przed valueOf.
-
Jeśli dowolna wartość operandu jest ciągiem znaków, wykonaj konkatenację
-
W przeciwnym razie przekonwertuj oba operandy na ich wartość liczbową, a następnie dodaj te wartości
Znajomość różnych wartości przymusu typów w JavaScript pomaga uprościć mylące wyjścia. Zobacz tabelę przymusu poniżej
+-----------------+-------------------+---------------+
| Primitive Value | String value | Numeric value |
+-----------------+-------------------+---------------+
| null | “null” | 0 |
| undefined | “undefined” | NaN |
| true | “true” | 1 |
| false | “false” | 0 |
| 123 | “123” | 123 |
| [] | “” | 0 |
| {} | “[object Object]” | NaN |
+-----------------+-------------------+---------------+
Dobrze jest również wiedzieć, że Operator + JavaScript jest lewostronnie asocjacyjny, ponieważ to określa, jakie dane wyjściowe będą dotyczyć więcej niż jednej operacji+.
Tak więc 1 + " 2 "DA " 12", ponieważ każdy dodatek zawierający łańcuch zawsze będzie domyślnie połączony łańcuchem.
Ty więcej przykładów można przeczytać w ten post na blogu (zastrzeżenie, które napisałem).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-04 16:42:36