Dlaczego 0 [0] jest poprawne składniowo?


119

Dlaczego ta linia jest poprawna w javascript?

var a = 0[0];

Potem ajest undefined.


4
jak true[0]lub ""[0]
Hacketo

24
@CodeAngry Aby być uczciwym, JavaScript narodził się w HTML i to właśnie HTML zapoczątkował całą rzecz "rzuć we mnie wszystko, co zechcesz, a spróbuję nadać temu sens".
Niet the Dark Absol

8
@NiettheDarkAbsol Szczerze mówiąc, po prostu się mylisz, ponieważ składnia ma sens (ale nie tak bardzo). To jest po prostu coraz właściwość o nazwie "0"z new Number(0)obiektu.
meandre

jest to fałszywe założenie, że a będzie zawsze niezdefiniowane. Całkowicie możliwe jest 0[0]zwrócenie wartości
Rune FS

@meandre 0["toString"]To niesamowite, dziękuję za wskazanie tego.
Jonathan

Odpowiedzi:


169

Kiedy to zrobisz 0[0], interpreter JS zamieni pierwszy obiekt 0w Numberobiekt, a następnie spróbuje uzyskać dostęp do [0]właściwości tego obiektu, którym jest undefined.

Nie ma błędu składniowego, ponieważ składnia dostępu do właściwości 0[0]jest dozwolona przez gramatykę języka w tym kontekście. Ta struktura (przy użyciu terminów z gramatyki JavaScript) to NumericLiteral[NumericLiteral].

Odpowiednia część gramatyki języka z sekcji A.3 specyfikacji ES5 ECMAScript jest następująca:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

Tak więc można śledzić gramatykę przez ten postęp:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

I podobnie Expressionmoże w końcu być NumericLiteraltak po wykonaniu gramatyki, widzimy, że jest to dozwolone:

NumericLiteral [ NumericLiteral ]

Co oznacza, że 0[0]jest to dozwolona część gramatyki, a zatem nie ma błędu SyntaxError.


Następnie w czasie wykonywania możesz odczytać właściwość, która nie istnieje (zostanie po prostu odczytana jako undefined), o ile źródło, z którego czytasz, jest obiektem lub ma niejawną konwersję na obiekt. A literał numeryczny rzeczywiście ma niejawną konwersję na obiekt (obiekt Number).

Jest to jedna z tych często nieznanych funkcji JavaScript. Rodzaje Number, Booleana Stringw JavaScript są zwykle przechowywane wewnętrznie jako prymitywów (nie pełnowymiarową obiektów). Są to zwarte, niezmienne reprezentacje pamięci masowej (prawdopodobnie zrobione w ten sposób dla wydajności implementacji). Ale Javascript chce, abyś mógł traktować te prymitywy jak obiekty z właściwościami i metodami. Tak więc, jeśli spróbujesz uzyskać dostęp do właściwości lub metody, która nie jest bezpośrednio obsługiwana w prymitywie, JavaScript tymczasowo przekształci prymityw w odpowiedni typ obiektu z wartością ustawioną na wartość prymitywu.

Kiedy używasz składni podobnej do obiektu w prymitywie, takim jak 0[0], interpreter rozpoznaje to jako dostęp do właściwości w prymitywie. Jego odpowiedzią na to jest pobranie pierwszego 0numerycznego prymitywu i przekształcenie go w pełnowymiarowy Numberobiekt, do którego może następnie uzyskać dostęp do [0]właściwości. W tym konkretnym przypadku [0]właściwość obiektu Number undefinedokreśla, dlaczego jest to wartość, z której otrzymujesz 0[0].

Oto artykuł o automatycznej konwersji prymitywu na obiekt do celów zajmowania się właściwościami:

Sekretne życie prymitywów Javascript


Oto odpowiednie fragmenty specyfikacji ECMAScript 5.1:

9.10 CheckObjectCoercible

Zgłasza TypeError, jeśli value to undefinedlub null, w przeciwnym razie zwraca true.

wprowadź opis obrazu tutaj

11.2.1 Akcesory właściwości

  1. Niech baseReference będzie wynikiem oceny MemberExpression.
  2. Niech baseValue będzie GetValue (baseReference).
  3. Niech propertyNameReference będzie wynikiem oceny wyrażenia.
  4. Niech propertyNameValue będzie GetValue (propertyNameReference).
  5. Wywołaj CheckObjectCoercible (baseValue).
  6. Niech propertyNameString będzie ToString (propertyNameValue).
  7. Jeśli oceniana produkcja składniowa jest zawarta w kodzie trybu ścisłego, niech ścisłe będzie prawdziwe, w przeciwnym razie ścisłe będzie fałszem.
  8. Zwraca wartość typu Reference, którego wartością podstawową jest baseValue i którego nazwa, do której istnieje odwołanie, to propertyNameString, i którego flaga trybu ścisłego jest ścisła.

Operacyjną częścią tego pytania jest krok # 5 powyżej.

8.7.1 GetValue (V)

Opisuje, jak gdy wartość, do której uzyskiwany jest dostęp, jest odwołaniem do właściwości, wywołuje ona w ToObject(base)celu pobrania wersji obiektu dowolnego prymitywu.

9.9 ToObject

Jak to opisano Boolean, Numberi Stringprymitywów przekształcany do postaci obiektów z [[PrimitiveValue]] Zgodnie z zestawu właściwości wewnętrznej.


Jako ciekawy test, jeśli kod wyglądałby tak:

var x = null;
var a = x[0];

Nadal nie wyrzuci błędu SyntaxError w czasie analizy, ponieważ jest to technicznie poprawna składnia, ale zgłosi błąd TypeError w czasie wykonywania kodu, ponieważ gdy powyższa logika Accessors właściwości zostanie zastosowana do wartości x, wywoła CheckObjectCoercible(x)lub wywoła ToObject(x)która oba wyrzucą TypeError, jeśli xjest nulllub undefined.


0[1,2]jest również ważne, co to znaczy? (Aktualizuję pytanie)
Michael M.

I nie powoduje to błędu składniowego, ponieważ dostęp do właściwości dla czegokolwiek, co nie jest nulllub undefinedjest całkowicie w porządku, nawet jeśli te właściwości nie istnieją.
user4642212

6
@Michael nie ma potrzeby aktualizacji. To jest operator przecinka, więc jest po prostu0[2]
Amit Joki

1
Operator przecinka: 1,2
szacuje

2
To niesamowita odpowiedź na niuanse pytania.
tbh__

20

Podobnie jak większość języków programowania, JS używa gramatyki do analizowania kodu i konwertowania go do postaci wykonywalnej. Jeśli w gramatyce nie ma reguły, którą można zastosować do określonego fragmentu kodu, zgłasza błąd SyntaxError. W przeciwnym razie kod jest uważany za ważny, bez względu na to, czy ma sens, czy nie.

Odpowiednie części gramatyki JS to

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Ponieważ 0[0]jest zgodny z tymi zasadami, jest uważane za prawidłowe wyrażenie. To, czy jest poprawne (np. Nie generuje błędu w czasie wykonywania), to inna historia, ale tak jest. W ten sposób JS ocenia wyrażenia takie jak someLiteral[someExpression]:

  1. oceniać someExpression(co może być arbitralnie złożone)
  2. przekonwertować literał na odpowiedni typ obiektu (literały numeryczne => Number, strings => Stringitd.)
  3. wywołaj get propertyoperację na result (2) z nazwą właściwości result (1)
  4. odrzuć wynik (2)

Tak 0[0]jest interpretowane jako

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

Oto przykład prawidłowego , ale niepoprawnego wyrażenia:

null[0]

Jest dobrze przeanalizowany, ale w czasie wykonywania interpreter kończy się niepowodzeniem w kroku 2 (ponieważ nullnie można go przekonwertować na obiekt) i zgłasza błąd czasu wykonywania.


1
Jest w tym coś więcej niż tylko to. var x = null; var a = x[0];nie generuje błędu składni, ale generuje błąd TypeError w czasie wykonywania.
jfriend00

@ jfriend00: nie o to chodziło w tym pytaniu, ale zostało dodane.
georg

wynik nie musi być niezdefiniowany. Możliwe jest 0[0]zwrócenie wartości zamiast niezdefiniowanej
Rune FS

9

Istnieją sytuacje, w których można poprawnie indeksować liczbę w JavaScript:

-> 0['toString']
function toString() { [native code] }

Chociaż nie od razu wiadomo, dlaczego chcesz to zrobić, indeksowanie w JavaScript jest równoważne używaniu notacji z kropkami (chociaż notacja z kropką ogranicza cię do używania identyfikatorów jako kluczy).


@AmitJoki To jest to samo co (0).toString(bez wywoływania funkcji). Jest to właściwość typu liczbowego.
user4642212

@AmitJoki, ponieważ odpowiada na pytanie „dlaczego ta linia jest ważna”.
Duncan

@Duncan, ale jest to bardziej „co to jest notacja w nawiasach” i przypuszczam, że OP to wie. Fakt, że jest on interpretowany jako obiekt Number, a następnie 0uzyskuje się dostęp do jego właściwości, a ponieważ nie istnieje, undefinedjest bardziej poprawny, jak wyjaśniono w jfriend00.
Amit Joki

@AmitJoki to niepoprawne założenie, 0[0]które zwróci wartość undefined. Prawdopodobnie tak się stanie, ale nie musi tak być
Rune FS

9

Chciałbym tylko zauważyć, że ta poprawna składnia nie jest w żaden sposób unikalna dla Javascript. Większość języków będzie miała błąd wykonania lub błąd typu, ale to nie to samo, co błąd składni. Javascript decyduje się zwrócić undefined w wielu sytuacjach, w których inny język może zgłosić wyjątek, w tym podczas indeksowania obiektu, który nie ma właściwości o podanej nazwie.

Składnia nie zna typu wyrażenia (nawet prostego wyrażenia, takiego jak literał numeryczny) i pozwoli ci zastosować dowolny operator do dowolnego wyrażenia. Na przykład próba indeksowania undefinedlub nullpowoduje, że TypeErrorw Javascript. To nie jest błąd składniowy - jeśli to nigdy nie zostanie wykonane (znajduje się po złej stronie instrukcji if), nie spowoduje to żadnych problemów, podczas gdy błąd składniowy jest z definicji zawsze wychwytywany w czasie kompilacji (eval, Function itp. , wszystkie liczą się jako kompilacja).


8

Ponieważ jest to poprawna składnia, a nawet poprawny kod do interpretacji. Możesz spróbować uzyskać dostęp do dowolnej właściwości dowolnego obiektu (w tym przypadku 0 zostanie rzutowane na obiekt Number), a to da ci wartość, jeśli istnieje, w przeciwnym razie niezdefiniowana. Próba uzyskania dostępu do właściwości undefined nie działa, więc 0 [0] [0] spowodowałaby błąd w czasie wykonywania. Jednak nadal byłoby to klasyfikowane jako poprawna składnia. Istnieje różnica w tym, jaka składnia jest poprawna, a co nie spowoduje błędów w czasie wykonywania / kompilacji.


3

Nie tylko składnia jest poprawna, ale wynik nie musi być, undefinedchociaż w większości, jeśli nie we wszystkich rozsądnych przypadkach, tak będzie. JS jest jednym z najbardziej czystych języków obiektowych. Większość tak zwanych języków obiektowych jest zorientowana na klasy, w tym sensie, że nie można zmienić formy (jest ona powiązana z klasą) utworzonego obiektu, a jedynie stan obiektu. W JS możesz zmienić stan oraz formę obiektu i robisz to częściej niż myślisz. Ta umiejętność sprawia, że ​​kod jest raczej niejasny, jeśli go niewłaściwie używasz. Liczby są niezmienne, więc nie możesz zmienić samego obiektu, ani jego stanu, ani formy, więc możesz to zrobić

0[0] = 1;

który jest prawidłowym wyrażeniem przypisania, które zwraca 1, ale w rzeczywistości niczego nie przypisuje. Liczba 0jest niezmienna. Co samo w sobie jest dość dziwne. Możesz mieć prawidłowe i poprawne (wykonywalne) wyrażenie przypisujące, które niczego nie przypisuje (*). Jednak typ cyfry jest zmiennym obiektem, więc można go zmodyfikować, a zmiany będą przebiegać kaskadowo w dół łańcucha prototypów.

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

oczywiście jest to dalekie od kategorii rozsądnego użycia, ale język jest określony, aby to umożliwić, ponieważ w innych scenariuszach rozszerzenie możliwości obiektów ma w rzeczywistości duży sens. W ten sposób wtyczki jQuery podpinają się do obiektu jQuery, aby dać przykład.

(*) W rzeczywistości przypisuje wartość 1 do właściwości obiektu, jednak nie ma możliwości odniesienia się do tego (transcient) obiektu, a zatem zostanie on zebrany przy przejściu nexx GC


3

W JavaScript wszystko jest obiektem, więc kiedy interpreter analizuje to, traktuje 0 jako obiekt i próbuje zwrócić 0 jako właściwość. To samo dzieje się, gdy próbujesz uzyskać dostęp do zerowego elementu true lub „” (pusty ciąg).

Nawet jeśli ustawisz 0 [0] = 1, ustawi właściwość i jej wartość w pamięci, ale gdy uzyskasz dostęp do 0, traktuje ją jako liczbę (nie myl tutaj traktowania jako obiektu i liczby).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.