Mengapa 0 [0] valid secara sintaksis?

119

Mengapa baris ini valid dalam javascript?

var a = 0[0];

Setelah itu, ais undefined.

Michael M.
sumber
4
sebagai true[0]atau ""[0]
Hacketo
24
@CodeAngry Agar adil JavaScript lahir dalam HTML, dan HTMLlah yang memulai keseluruhan hal "lemparkan apa pun yang Anda suka ke saya dan saya akan mencoba memahaminya".
Niet the Dark Absol
8
@NiettheDarkAbsol Agar adil Anda hanya salah sebagai sintaks tidak masuk akal (namun tidak begitu banyak). Ini hanya mendapatkan properti bernama "0"dari sebuah new Number(0)objek.
meandre
itu adalah asumsi yang salah bahwa akan selalu tidak terdefinisi. Sangat mungkin untuk 0[0]mengembalikan nilai
Rune FS
@meandre 0["toString"]Itu luar biasa, terima kasih telah menunjukkannya.
Jonathan

Jawaban:

169

Ketika Anda melakukannya 0[0], juru bahasa JS akan mengubah yang pertama 0menjadi Numberobjek dan kemudian mencoba mengakses [0]properti objek itu undefined.

Tidak ada kesalahan sintaks karena sintaks akses properti 0[0]diperbolehkan oleh tata bahasa dalam konteks ini. Struktur ini (menggunakan istilah dalam tata bahasa Javascript) adalah NumericLiteral[NumericLiteral].

Bagian yang relevan dari tata bahasa dari bagian A.3 dari spesifikasi ES5 ECMAScript adalah ini:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

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

Jadi, seseorang dapat mengikuti grammer melalui perkembangan ini:

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

Dan, demikian Expressionpula pada akhirnya bisa NumericLiteraljadi setelah mengikuti tata bahasa, kita melihat bahwa ini diperbolehkan:

NumericLiteral [ NumericLiteral ]

Yang berarti itu 0[0]adalah bagian tata bahasa yang diizinkan dan karenanya tidak ada SyntaxError.


Kemudian, pada waktu proses Anda diizinkan untuk membaca properti yang tidak ada (hanya akan dibaca sebagai undefined) selama sumber yang Anda baca adalah objek atau memiliki konversi implisit ke objek. Dan, literal numerik memang memiliki konversi implisit menjadi objek (objek Angka).

Ini adalah salah satu fitur Javascript yang sering tidak dikenal. Jenis Number, Booleandan Stringdalam Javascript biasanya disimpan secara internal sebagai primitif (bukan objek yang lengkap). Ini adalah representasi penyimpanan yang kompak dan tidak dapat diubah (mungkin dilakukan dengan cara ini untuk efisiensi implementasi). Namun, Javascript ingin Anda dapat memperlakukan objek primitif ini seperti objek dengan properti dan metode. Jadi, jika Anda mencoba mengakses properti atau metode yang tidak didukung secara langsung pada primitif, maka Javascript akan memaksa primitif untuk sementara menjadi tipe objek yang sesuai dengan nilai yang ditetapkan ke nilai primitif.

Saat Anda menggunakan sintaks mirip objek pada primitif seperti 0[0], interpreter mengenali ini sebagai akses properti pada primitif. Responsnya terhadap hal ini adalah dengan mengambil 0primitif numerik pertama dan memaksanya menjadi objek full-blown Numberyang kemudian dapat mengakses [0]properti. Dalam kasus khusus ini, [0]properti pada objek Angka undefineditulah sebabnya itulah nilai yang Anda dapatkan 0[0].

Berikut adalah artikel tentang konversi otomatis dari primitif menjadi objek untuk tujuan menangani properti:

Kehidupan Rahasia Primitif Javascript


Berikut adalah bagian yang relevan dari spesifikasi ECMAScript 5.1:

9.10 CheckObjectCoercible

Melempar TypeError jika nilainya adalah undefinedatau null, jika tidak, dikembalikan true.

masukkan deskripsi gambar di sini

11.2.1 Pengakses Properti

  1. Biarkan baseReference menjadi hasil evaluasi MemberExpression.
  2. Biarkan baseValue menjadi GetValue (baseReference).
  3. Biarkan propertyNameReference menjadi hasil evaluasi Expression.
  4. Biarkan propertyNameValue menjadi GetValue (propertyNameReference).
  5. Panggil CheckObjectCoercible (baseValue).
  6. Biarkan propertyNameString menjadi ToString (propertyNameValue).
  7. Jika produksi sintaksis yang sedang dievaluasi terdapat dalam kode mode ketat, biarkan ketat menjadi benar, kalau tidak biarkan ketat menjadi salah.
  8. Kembalikan nilai tipe Reference yang nilai dasarnya adalah baseValue dan nama referensinya adalah propertyNameString, dan yang flag mode ketatnya ketat.

Bagian operasi untuk pertanyaan ini adalah langkah # 5 di atas.

8.7.1 GetValue (V)

Ini menjelaskan bagaimana ketika nilai yang diakses adalah referensi properti, ia memanggil ToObject(base)untuk mendapatkan versi objek dari primitif apa pun.

9.9 ToObject

Ini menjelaskan bagaimana Boolean, Numberdan Stringprimitif dikonversi ke bentuk objek dengan properti internal [[PrimitiveValue]] disetel sesuai.


Sebagai tes yang menarik, jika kodenya seperti ini:

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

Itu masih tidak akan memunculkan SyntaxError pada waktu parse karena ini secara teknis adalah sintaks yang legal, tetapi itu akan memunculkan TypeError saat runtime ketika Anda menjalankan kode karena ketika logika Pengakses Properti di atas diterapkan ke nilai x, itu akan memanggil CheckObjectCoercible(x)atau memanggil ToObject(x)yang mana akan menampilkan TypeError jika xadalah nullatau undefined.

jfriend00
sumber
0[1,2]juga valid, apa artinya? (Saya memperbarui pertanyaan)
Michael M.
Dan itu tidak menimbulkan kesalahan sintaks, karena mengakses properti pada apa pun yang tidak nullatau undefinedsama sekali baik-baik saja, bahkan jika properti itu tidak ada.
pengguna4642212
6
@Michael tidak perlu memperbarui. Itu adalah operator koma jadi hanya0[2]
Amit Joki
1
Operator koma: mengevaluasi 1 dan 2 dalam 1,2tetapi mengembalikan 2.
user4642212
2
Itu adalah jawaban yang luar biasa untuk nuansa pertanyaan.
tbh__
20

Seperti kebanyakan bahasa pemrograman, JS menggunakan tata bahasa untuk mengurai kode Anda dan mengubahnya menjadi bentuk yang dapat dieksekusi. Jika tidak ada aturan dalam tata bahasa yang dapat diterapkan ke potongan kode tertentu, SyntaxError akan muncul. Jika tidak, kode dianggap valid, tidak peduli apakah masuk akal atau tidak.

Bagian yang relevan dari tata bahasa JS adalah

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Karena 0[0]sesuai dengan aturan ini, ini dianggap sebagai ekspresi yang valid . Apakah itu benar (misalnya tidak menimbulkan kesalahan pada waktu proses) adalah cerita lain, tapi ya itu benar. Beginilah cara JS mengevaluasi ekspresi seperti someLiteral[someExpression]:

  1. evaluasi someExpression(yang dapat menjadi kompleks sewenang-wenang)
  2. konversikan literal ke tipe objek yang sesuai (literal numerik => Number, string => Stringdll)
  3. panggil get propertyoperasi pada hasil (2) dengan hasil nama properti (1)
  4. buang hasil (2)

Jadi 0[0]diartikan sebagai

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

Berikut adalah contoh ekspresi yang valid , tetapi salah :

null[0]

Ini diuraikan dengan baik, tetapi pada waktu proses, penafsir gagal pada langkah 2 (karena nulltidak dapat diubah menjadi objek) dan memunculkan kesalahan waktu proses.

georg
sumber
1
Ada lebih dari itu. var x = null; var a = x[0];tidak menghasilkan kesalahan sintaks, tetapi memunculkan TypeError saat runtime.
jfriend00
@ jfriend00: Bukan itu pertanyaannya, tapi ditambahkan.
georg
hasilnya tidak harus tidak ditentukan. Dimungkinkan untuk 0[0]mengembalikan nilai alih-alih tidak ditentukan
Rune FS
9

Ada situasi di mana Anda dapat secara valid memasukkan angka dalam Javascript:

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

Meskipun tidak segera jelas mengapa Anda ingin melakukan ini, berlangganan dalam Javascript sama dengan menggunakan notasi titik (meskipun notasi titik membatasi Anda untuk menggunakan pengenal sebagai kunci).

Duncan
sumber
@AmitJoki Ini sama dengan (0).toString(tanpa memanggil fungsi). Ini adalah properti dari tipe angka.
pengguna4642212
@AmitJoki karena menjawab pertanyaan 'mengapa baris ini valid'.
Duncan
@ Duncan tapi ini lebih dari "apa itu braket notasi" dan saya kira OP tahu itu. Fakta bahwa itu ditafsirkan sebagai Nomor obyek dan kemudian nya 0properti diakses dan karena itu tidak ada, undefinedyang lebih tepat seperti yang dijelaskan dalam jfriend00.
Amit Joki
@AmitJoki itu adalah asumsi yang salah yang 0[0]akan kembali tidak terdefinisi. Kemungkinan akan terjadi, tetapi tidak harus demikian
Rune FS
9

Saya hanya ingin mencatat bahwa sintaks yang valid ini sama sekali tidak unik untuk Javascript. Sebagian besar bahasa akan memiliki kesalahan waktu proses atau kesalahan jenis, tetapi itu tidak sama dengan kesalahan sintaks. Javascript memilih untuk mengembalikan tak terdefinisi dalam banyak situasi di mana bahasa lain mungkin memunculkan pengecualian, termasuk saat memasukkan objek yang tidak memiliki properti dari nama yang diberikan.

Sintaks tidak mengetahui jenis ekspresi (bahkan ekspresi sederhana seperti literal numerik), dan memungkinkan Anda menerapkan operator apa pun ke ekspresi apa pun. Misalnya, mencoba untuk subskrip undefinedatau nullmenyebabkan TypeErrordi Javascript. Ini bukan kesalahan sintaks - jika ini tidak pernah dijalankan (berada di sisi yang salah dari pernyataan-if), itu tidak akan menimbulkan masalah, sedangkan kesalahan sintaks secara definisi selalu tertangkap pada waktu kompilasi (eval, Fungsi, dll. , semuanya dihitung sebagai kompilasi).

Acak832
sumber
8

Karena itu sintaks yang valid, dan bahkan kode yang valid untuk diinterpretasikan. Anda dapat mencoba mengakses properti apa pun dari objek apa pun (dan dalam kasus ini 0 akan dilemparkan ke objek Angka), dan ini akan memberi Anda nilai jika ada, jika tidak, tidak ditentukan. Namun, mencoba mengakses properti undefined tidak berhasil, jadi 0 [0] [0] akan menghasilkan error runtime. Ini masih akan diklasifikasikan sebagai sintaks yang valid. Ada perbedaan antara sintaks yang valid dan apa yang tidak menyebabkan kesalahan waktu proses / waktu kompilasi.

Clox
sumber
3

Tidak hanya sintaksnya valid, hasilnya tidak harus seperti itu undefineddi sebagian besar, jika tidak semua kasus waras itu akan terjadi. JS adalah salah satu bahasa berorientasi objek paling murni. Kebanyakan yang disebut bahasa OO adalah berorientasi kelas, dalam arti bahwa Anda tidak dapat mengubah bentuk (terikat pada kelas) dari objek setelah dibuat, hanya status objek. Di JS Anda dapat mengubah status serta bentuk objek dan ini Anda lakukan lebih sering daripada yang Anda pikirkan. Kemampuan ini membuat kode yang agak tidak jelas, jika Anda menyalahgunakannya. Angka tidak dapat diubah, jadi Anda tidak dapat mengubah objek itu sendiri, baik itu status maupun bentuknya sehingga Anda dapat melakukannya

0[0] = 1;

yang merupakan ekspresi penugasan valid yang mengembalikan 1 tetapi tidak benar-benar menetapkan apa pun, Angka 0tidak dapat diubah. Yang dengan sendirinya agak aneh. Anda dapat memiliki ekspresi assingment yang valid dan benar (dapat dijalankan), yang tidak menetapkan apa pun (*). Namun tipe angka adalah objek yang bisa berubah sehingga Anda bisa mengubah tipe, dan perubahan akan menuruni rantai prototipe.

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]); 

tentu saja ini jauh dari kategori penggunaan yang waras tetapi bahasanya ditentukan untuk memungkinkan hal ini karena dalam skenario lain, memperluas kemampuan objek sebenarnya sangat masuk akal. Begitulah cara plugin jQuery menghubungkan ke objek jQuery untuk memberikan contoh.

(*) Ini benar-benar menetapkan nilai 1 ke properti objek, namun tidak ada cara Anda dapat mereferensikan objek (transcient) itu dan dengan demikian akan dikumpulkan di nexx GC pass

Rune FS
sumber
3

Dalam JavaScript, semuanya adalah objek, jadi ketika interpreter menguraikannya, ia memperlakukan 0 sebagai objek dan mencoba mengembalikan 0 sebagai properti. Hal yang sama terjadi saat Anda mencoba mengakses elemen ke 0 dari true atau "" (string kosong).

Meskipun Anda menyetel 0 [0] = 1, ini akan menyetel properti dan nilainya dalam memori, tetapi saat Anda mengakses 0, ia diperlakukan sebagai angka (Jangan bingung antara memperlakukan sebagai Objek dan angka di sini.)

Laxmikant Dange
sumber