Berdasarkan spesifikasi, hanya dua tipe primitif yang dapat berfungsi sebagai kunci properti objek:
tipe string, atau
tipe simbol.
Jika tidak, jika seseorang menggunakan tipe lain, seperti angka, maka akan otomatis dikonversi menjadi string. Sehingga obj[1]
sama dengan obj["1"]
, dan obj[true]
sama dengan obj["true"]
.
Sampai saat ini kami hanya menggunakan string.
Sekarang mari kita jelajahi simbol-simbol, lihat manfaatnya bagi kita.
Sebuah "simbol" mewakili pengidentifikasi unik.
Nilai jenis ini dapat dibuat menggunakan Symbol()
:
biarkan id = Simbol();
Setelah dibuat, kita dapat memberikan deskripsi simbol (juga disebut nama simbol), yang sebagian besar berguna untuk tujuan debugging:
// id adalah simbol dengan keterangan "id" biarkan id = Simbol("id");
Simbol dijamin unik. Sekalipun kita membuat banyak simbol dengan deskripsi yang sama persis, nilainya berbeda. Deskripsi hanyalah label yang tidak mempengaruhi apapun.
Misalnya, berikut adalah dua simbol dengan deskripsi yang sama – keduanya tidak sama:
biarkan id1 = Simbol("id"); biarkan id2 = Simbol("id"); peringatan(id1 == id2); // PALSU
Jika Anda familiar dengan Ruby atau bahasa lain yang juga memiliki semacam “simbol” – mohon jangan salah kaprah. Simbol JavaScript berbeda.
Jadi, untuk meringkas, simbol adalah “nilai unik primitif” dengan deskripsi opsional. Mari kita lihat di mana kita bisa menggunakannya.
Simbol tidak otomatis dikonversi menjadi string
Sebagian besar nilai dalam JavaScript mendukung konversi implisit ke string. Misalnya, kita dapat alert
hampir semua nilai, dan itu akan berhasil. Simbol itu istimewa. Mereka tidak melakukan konversi otomatis.
Misalnya, alert
ini akan menampilkan kesalahan:
biarkan id = Simbol("id"); peringatan(id); // TypeError: Tidak dapat mengonversi nilai Simbol menjadi string
Itu adalah “penjaga bahasa” agar tidak terjadi kesalahan, karena string dan simbol pada dasarnya berbeda dan tidak boleh diubah satu sama lain secara tidak sengaja.
Jika kita benar-benar ingin menampilkan simbol, kita perlu memanggil .toString()
secara eksplisit, seperti di sini:
biarkan id = Simbol("id"); peringatan(id.toString()); // Simbol(id), sekarang berfungsi
Atau dapatkan properti symbol.description
untuk menampilkan deskripsi saja:
biarkan id = Simbol("id"); alert(id.deskripsi); // pengenal
Simbol memungkinkan kita membuat properti “tersembunyi” dari suatu objek, sehingga tidak ada bagian kode lain yang dapat mengakses atau menimpanya secara tidak sengaja.
Misalnya, jika kita bekerja dengan objek user
, yang merupakan milik kode pihak ketiga. Kami ingin menambahkan pengidentifikasi ke dalamnya.
Mari kita gunakan kunci simbol untuk itu:
biarkan pengguna = { // menjadi milik kode lain nama: "Yohanes" }; biarkan id = Simbol("id"); pengguna[id] = 1; peringatan( pengguna[id] ); // kita dapat mengakses data menggunakan simbol sebagai kuncinya
Apa manfaat menggunakan Symbol("id")
dibandingkan string "id"
?
Karena objek user
milik basis kode lain, tidak aman untuk menambahkan bidang ke objek tersebut, karena kita mungkin memengaruhi perilaku yang telah ditentukan sebelumnya dalam basis kode lain tersebut. Namun, simbol tidak dapat diakses secara tidak sengaja. Kode pihak ketiga tidak akan mengetahui simbol yang baru ditentukan, jadi aman untuk menambahkan simbol ke objek user
.
Juga, bayangkan skrip lain ingin memiliki pengenalnya sendiri di dalam user
, untuk tujuannya sendiri.
Kemudian skrip tersebut dapat membuat Symbol("id")
sendiri, seperti ini:
// ... biarkan id = Simbol("id"); user[id] = "Nilai id mereka";
Tidak akan ada konflik antara pengidentifikasi kami dan pengidentifikasinya, karena simbol selalu berbeda, meskipun namanya sama.
…Tetapi jika kita menggunakan string "id"
dan bukan simbol untuk tujuan yang sama, maka akan terjadi konflik:
biarkan pengguna = { nama: "John" }; // Skrip kita menggunakan properti "id". user.id = "Nilai id kami"; // ...Skrip lain juga menginginkan "id" untuk tujuannya... user.id = "Nilai id mereka" // Ledakan! ditimpa oleh skrip lain!
Jika kita ingin menggunakan simbol dalam literal objek {...}
, kita memerlukan tanda kurung siku di sekelilingnya.
Seperti ini:
biarkan id = Simbol("id"); biarkan pengguna = { nama: "Yohanes", [id]: 123 // bukan "id": 123 };
Itu karena kita membutuhkan nilai dari variabel id
sebagai kuncinya, bukan string “id”.
Properti simbolik tidak berpartisipasi dalam loop for..in
.
Misalnya:
biarkan id = Simbol("id"); biarkan pengguna = { nama: "Yohanes", usia: 30, [id]: 123 }; for (biarkan memasukkan pengguna) alert(key); // nama, umur (tanpa simbol) // akses langsung dengan simbol berfungsi alert("Langsung: " + pengguna[id] ); // Langsung: 123
Object.keys(user) juga mengabaikannya. Itu adalah bagian dari prinsip umum “menyembunyikan sifat simbolis”. Jika skrip atau pustaka lain mengulang objek kita, ia tidak akan mengakses properti simbolik secara tidak terduga.
Sebaliknya, Object.assign menyalin properti string dan simbol:
biarkan id = Simbol("id"); biarkan pengguna = { [id]: 123 }; biarkan clone = Object.assign({}, pengguna); waspada( klon[id] ); // 123
Tidak ada paradoks di sini. Itu memang disengaja. Idenya adalah ketika kita mengkloning suatu objek atau menggabungkan objek, kita biasanya ingin semua properti disalin (termasuk simbol seperti id
).
Seperti yang telah kita lihat, biasanya semua simbol berbeda, meskipun namanya sama. Namun terkadang kita ingin simbol dengan nama yang sama menjadi entitas yang sama. Misalnya, bagian berbeda dari aplikasi kita ingin mengakses simbol "id"
yang berarti properti yang sama persis.
Untuk mencapai hal itu, terdapat registri simbol global . Kita dapat membuat simbol di dalamnya dan mengaksesnya nanti, dan ini menjamin bahwa akses berulang dengan nama yang sama akan menghasilkan simbol yang persis sama.
Untuk membaca (membuat jika tidak ada) simbol dari registri, gunakan Symbol.for(key)
.
Panggilan itu memeriksa registri global, dan jika ada simbol yang dideskripsikan sebagai key
, lalu mengembalikannya, jika tidak, buat simbol baru Symbol(key)
dan simpan di registri dengan key
yang diberikan.
Misalnya:
// membaca dari registri global biarkan id = Simbol.untuk("id"); // jika simbol tidak ada, maka simbol tersebut dibuat // membacanya lagi (mungkin dari bagian kode yang lain) biarkan idAgain = Simbol.untuk("id"); // simbol yang sama waspada( id === idLagi ); // BENAR
Simbol di dalam registri disebut simbol global . Jika kita menginginkan simbol aplikasi yang luas, dapat diakses di mana saja dalam kode – itulah gunanya.
Kedengarannya seperti Ruby
Dalam beberapa bahasa pemrograman, seperti Ruby, hanya ada satu simbol untuk setiap nama.
Dalam JavaScript, seperti yang bisa kita lihat, hal ini berlaku untuk simbol global.
Kita telah melihat bahwa untuk simbol global, Symbol.for(key)
mengembalikan simbol berdasarkan nama. Untuk melakukan sebaliknya – mengembalikan nama dengan simbol global – kita dapat menggunakan: Symbol.keyFor(sym)
:
Misalnya:
// dapatkan simbol berdasarkan nama biarkan sym = Simbol.untuk("nama"); biarkan sym2 = Simbol.untuk("id"); // dapatkan nama berdasarkan simbol alert( Simbol.keyFor(sym) ); // nama peringatan( Simbol.keyFor(sym2) ); // pengenal
Symbol.keyFor
secara internal menggunakan registri simbol global untuk mencari kunci simbol tersebut. Jadi ini tidak berlaku untuk simbol non-global. Jika simbolnya tidak global, simbol tersebut tidak akan dapat menemukannya dan mengembalikan undefined
.
Meskipun demikian, semua simbol memiliki properti description
.
Misalnya:
biarkan globalSymbol = Simbol.untuk("nama"); biarkan localSymbol = Simbol("nama"); waspada( Simbol.keyFor(globalSymbol) ); // nama, simbol global peringatan( Simbol.kunciUntuk(Simbol lokal) ); // tidak terdefinisi, bukan global peringatan( localSymbol.deskripsi ); // nama
Ada banyak simbol “sistem” yang digunakan JavaScript secara internal, dan kita dapat menggunakannya untuk menyempurnakan berbagai aspek objek kita.
Mereka tercantum dalam spesifikasi di tabel Simbol terkenal:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…dan sebagainya.
Misalnya, Symbol.toPrimitive
memungkinkan kita mendeskripsikan objek ke konversi primitif. Kita akan segera melihat kegunaannya.
Simbol-simbol lain juga akan menjadi familiar ketika kita mempelajari ciri-ciri bahasa yang bersangkutan.
Symbol
adalah tipe primitif untuk pengidentifikasi unik.
Simbol dibuat dengan panggilan Symbol()
dengan deskripsi opsional (nama).
Simbol selalu mempunyai nilai yang berbeda, meskipun memiliki nama yang sama. Jika kita ingin simbol dengan nama yang sama sama, maka kita harus menggunakan registri global: Symbol.for(key)
mengembalikan (membuat jika diperlukan) simbol global dengan key
sebagai namanya. Beberapa panggilan Symbol.for
dengan key
yang sama menghasilkan simbol yang persis sama.
Simbol memiliki dua kasus penggunaan utama:
Properti objek "Tersembunyi".
Jika kita ingin menambahkan properti ke dalam objek yang “milik” skrip atau pustaka lain, kita dapat membuat simbol dan menggunakannya sebagai kunci properti. Properti simbolik tidak muncul di for..in
, sehingga tidak akan diproses secara tidak sengaja bersama dengan properti lainnya. Juga tidak akan bisa diakses secara langsung, karena skrip lain tidak memiliki simbol kita. Jadi properti akan terlindungi dari penggunaan atau penimpaan yang tidak disengaja.
Jadi kita bisa “diam-diam” menyembunyikan sesuatu ke dalam objek yang kita perlukan, namun orang lain tidak boleh melihatnya, dengan menggunakan properti simbolik.
Ada banyak simbol sistem yang digunakan oleh JavaScript yang dapat diakses sebagai Symbol.*
. Kita dapat menggunakannya untuk mengubah beberapa perilaku bawaan. Misalnya, nanti di tutorial kita akan menggunakan Symbol.iterator
untuk iterable, Symbol.toPrimitive
untuk mengatur konversi objek-ke-primitif dan seterusnya.
Secara teknis, simbol tidak 100% tersembunyi. Ada metode bawaan Object.getOwnPropertySymbols(obj) yang memungkinkan kita mendapatkan semua simbol. Juga ada metode bernama Reflect.ownKeys(obj) yang mengembalikan semua kunci suatu objek termasuk kunci simbolis. Namun sebagian besar perpustakaan, fungsi bawaan, dan konstruksi sintaksis tidak menggunakan metode ini.