Analiza przykładów JavaScript w prelekcji Gary ’ ego Bernhardta „Wat”

ten post jest hołdem dla fantastycznego wykładu Gary ’ ego Bernhardta „Wat”, w którym zwraca uwagę na specyfikę niektórych konstrukcji językowych w Ruby i JavaScript. Jeśli jeszcze nie oglądałeś rozmowy, zdecydowanie polecam poświęcić trochę czasu i zrobić dokładnie to! To tylko jakieś 4 minuty i bardzo zabawne, obiecuję.

w swoim wykładzie Gary pokazuje te cztery fragmenty kodu JavaScript:

osobliwości w JavaScript

widzimy wiele nawiasów, nawiasów i znaków plus. Oto co te fragmenty oceniają na:

  • + == ""
  • + {} == ""
  • {} + == 0
  • {} + {} == NaN

kiedy zobaczyłem te przykłady po raz pierwszy, pomyślałem: „Wow, to wygląda niechlujnie!”Wyniki mogą wydawać się niespójne lub nawet arbitralne, ale proszę o cierpliwość. Wszystkie te przykłady są rzeczywiście bardzo spójne i nie tak źle, jak wyglądają!

#Fragment #1: +

zacznijmy od pierwszego fragmentu:

 + // ""

jak widać, zastosowanie operatora + do dwóch pustych tablic powoduje powstanie pustego łańcucha. Dzieje się tak, ponieważ reprezentacja łańcuchowa tablicy jest reprezentacją łańcuchową wszystkich jej elementów, połączonych ze przecinkami:

.toString()// "1,2,3".toString()// "1,2".toString()// "1".toString()// ""

pusta tablica nie zawiera żadnych elementów, więc jej reprezentacja łańcuchowa jest pustym łańcuchem. Zatem połączenie dwóch pustych łańcuchów jest po prostu kolejnym pustym łańcuchem.

#Fragment #2: + {}

jak na razie dobrze. Przyjrzyjmy się teraz drugiemu fragmentowi:

 + {}// ""

zauważ, że ponieważ nie mamy do czynienia z dwiema liczbami, operator + ponownie wykonuje konkatenację łańcuchów zamiast dodawania dwóch wartości liczbowych.

w poprzedniej sekcji widzieliśmy już, że reprezentacja łańcuchów pustej tablicy jest pustym łańcuchem. Łańcuchowa reprezentacja pustego obiektu jest tutaj wartością domyślną "". Prepending pusty łańcuch nie zmienia wartości, więc "" jest wynikiem końcowym.

w JavaScript obiekty mogą zaimplementować specjalną metodę o nazwie toString(), która zwraca niestandardową reprezentację ciągu znaków obiektu, na którym metoda jest wywoływana. Nasz pusty literal obiektu nie implementuje takiej metody, więc wracamy do domyślnej implementacji prototypu Object.

#Fragment #3: {} +

twierdzę, że do tej pory wyniki nie były zbyt nieoczekiwane. Po prostu przestrzegali zasad przymusu typu i domyślnych reprezentacji ciągów znaków w JavaScript.

jednak {} + to tam Programiści zaczynają się mylić:

{} + // 0

dlaczego widzimy 0 (liczba zero), jeśli wpisujemy powyższą linię w REPL JavaScript, jak konsola przeglądarki? Czy wynik nie powinien być ciągiem znaków, tak jak + {}?

zanim rozwiążemy zagadkę, rozważ trzy różne sposoby użycia operatora + :

// 1) Addition of two numeric values2 + 2 == 4// 2) String concatenation of two values"2" + "2" == "22"// 3) Conversion of a value to a number+2 == 2+"2" == 2

w pierwszych dwóch przypadkach operator + jest operatorem binarnym, ponieważ ma dwa operandy (po lewej i po prawej). W trzecim przypadku operator + jest operatorem jednoargumentowym, ponieważ ma tylko jeden operand (po prawej).

rozważ również dwa możliwe znaczenia {} w JavaScript. Zwykle zapisujemy {} jako pusty literał obiektu, ale jeśli znajdujemy się w pozycji polecenia, Gramatyka JavaScript określa {} jako pusty blok. Poniższy fragment kodu definiuje dwa puste bloki, z których żaden nie jest dosłownym obiektem:

{}// Empty block{ // Empty block}

spójrzmy jeszcze raz na nasz fragment:

{} + 

pozwól mi zmienić trochę białe spacje, aby było jaśniejsze, jak silnik JavaScript widzi kod:

{ // Empty block}+;

teraz możemy wyraźnie zobaczyć, co się tu dzieje. Mamy instrukcję block, po której następuje kolejna instrukcja, która zawiera jednoznakowe wyrażenie + działające na pustej tablicy. Średnik końcowy jest wstawiany automatycznie zgodnie z regułami asi (automatic semicolon insertion).

możesz łatwo sprawdzić w konsoli przeglądarki, że +ocenia 0. Pusta tablica ma pusty łańcuch jako swoją reprezentację łańcuchową, która z kolei jest konwertowana na liczbę zero przez operatora +. Wreszcie, wartość ostatniej instrukcji (w tym przypadku+) jest zgłaszana przez konsolę przeglądarki.

Alternatywnie, możesz podać oba fragmenty kodu do parsera JavaScript, takiego jak Esprima, i porównać wynikowe abstrakcyjne drzewa składniowe. Oto AST dla + {}:

{ "type": "Program", "body": }, "right": { "type": "ObjectExpression", "properties": } } } ], "sourceType": "script"}

a oto AST dla {} + :

{ "type": "Program", "body": }, { "type": "ExpressionStatement", "expression": { "type": "UnaryExpression", "operator": "+", "argument": { "type": "ArrayExpression", "elements": }, "prefix": true } } ], "sourceType": "script"}

zamieszanie wynika z niuansów gramatyki JavaScript, która używa nawiasów klamrowych zarówno dla literałów obiektów, jak i bloków. W pozycji polecenia nawias otwierający rozpoczyna blok, podczas gdy w pozycji wyrażenia nawias otwierający rozpoczyna literał obiektu.

#Fragment #4: {} + {}

na koniec, rzućmy szybko okiem na nasz ostatni fragment {} + {}:

{} + {}// NaN

cóż, dodanie dwóch literałów obiektowych to dosłownie „nie Liczba” – ale czy dodajemy tutaj dwa literały obiektowe? Nie daj się znowu zwieść aparatom! Oto co się dzieje:

{ // Empty block}+{};

jest to prawie taki sam układ jak w poprzednim przykładzie. Jednak teraz stosujemy Operator uniary plus do pustego literału obiektu. To jest w zasadzie to samo co Robienie Number({}), co skutkuje NaN, ponieważ nasz literal obiektu nie może być skonwertowany na liczbę.

jeśli chcesz, aby silnik JavaScript przetwarzał kod jako dwa puste literały obiektu, zawiń pierwszy (lub cały fragment kodu) w nawiasy. Powinieneś teraz zobaczyć oczekiwany wynik:

({}) + {}// ""({} + {})// ""

nawias otwierający powoduje, że parser próbuje rozpoznać wyrażenie, dlatego nie traktuje {} jako bloku (który byłby instrukcją).

#podsumowanie

powinieneś teraz zobaczyć, dlaczego cztery fragmenty kodu oceniają sposób, w jaki działają. Nie jest to w ogóle arbitralne ani przypadkowe; Zasady przymusu typu są stosowane dokładnie tak, jak określono w specyfikacji i gramatyce języka.

pamiętaj tylko, że jeśli nawias otwierający jest pierwszym znakiem pojawiającym się w instrukcji, będzie on interpretowany jako początek bloku, a nie literał obiektu.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.