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.

 758
Author: Magne, 2012-01-27

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 .

  1. [] + []

    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łania object.toString() (§8.12.8). Dla tablic jest to to samo co wywołanie array.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.

  2. [] + {}

    Podobnie jak [] + [], oba operandy są najpierw konwertowane na prymitywy. Dla "Object objects" (§15.2) jest to ponownie wynik wywołania object.toString(), które dla non-null, Undefined obiekty to "[object Object]" (§15.2.4.2).

  3. {} + []

    {} 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) zwraca ToNumber(ToPrimitive(operand)). Jak już wiemy, {[13] } jest ciągiem pustym, a według §9.3.1, ToNumber("") jest 0.

  4. {} + {}

    Podobnie jak w poprzednim przypadku, pierwszy {[9] } jest przetwarzany jako blok z pustą zwracaną wartością. Ponownie, +{} jest tym samym co ToNumber(ToPrimitive({})), a ToPrimitive({}) jest "[object Object]" (Zobacz [] + {}). Aby uzyskać wynik +{}, musimy zastosować {[23] } na łańcuchu "[object Object]". Wykonując kroki z §9.3.1, otrzymujemy NaN w wyniku:

    Jeśli gramatyka nie może zinterpretować ciągu jako rozszerzenia StringNumericLiteral , wtedy wynikiem ToNumber jest NaN .

  5. 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), natomiast ToNumber("wat") znowu jest {[25] } Jak na §9.3.1. Po 7. kroku §11.6.2, §11.6.3 dyktuje, że

    Jeśli któryś z operandów to NaN , wynikiem jest NaN .

    Więc argumentem do Array(16).join jest NaN. 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 konwertuje 1 na ciąg znaków zamiast "wat" na liczbę, więc skutecznie wywołuje Array(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.

 1489
Author: Ventero,
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('&gt;&gt;&gt; ' + 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 ().]

 30
Author: CR Drost,
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:

  1. Call obj.valueOf(). Jeśli zwróci prymityw, jesteś skończony. Bezpośrednie instancje Object i tablic zwracają się same, więc to jeszcze nie koniec.
  2. Call obj.toString(). Jeśli zwróci prymityw, jesteś skończony. {} i [] obie zwracają ciąg znaków, więc gotowe.
  3. 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?"

 20
Author: Axel Rauschmayer,
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 lub toString, 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 jak String([]) + 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 jak String([]) + 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óbuje valueOf, która w przypadku tablic zwraca ten sam obiekt, a następnie próbuje ostateczności: konwersja toString 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ę. Otrzymujemy NaN, a każda operacja arytmetyczna na NaN daje wynik z NaN, więc mamy: Array(16).join(NaN).
 13
Author: Mariusz Nowak,
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

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

  1. Jeśli dowolna wartość operandu jest ciągiem znaków, wykonaj konkatenację

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

 0
Author: AbdulFattah Popoola,
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