Karakter primitif adalah salah satu inovasi standar ES6, yang membawa beberapa fitur berharga ke JavaScript. Simbol yang diwakili oleh tipe data Simbol sangat berguna ketika digunakan sebagai pengidentifikasi untuk properti objek. Sehubungan dengan skenario penerapan mereka, muncul pertanyaan tentang apa yang mereka bisa, apa yang tidak bisa dilakukan oleh garis itu.

Dalam materi, terjemahan yang kami terbitkan hari ini, kami akan berbicara tentang tipe data Simbol dalam JavaScript. Kami akan mulai dengan meninjau beberapa fitur JavaScript yang perlu Anda navigasikan untuk menangani simbol.
Informasi awal
Dalam JavaScript, sebenarnya, ada dua jenis nilai. Tipe pertama - nilai primitif, objek kedua (mereka juga menyertakan fungsi). Nilai-nilai primitif meliputi tipe data sederhana seperti angka (ini termasuk semuanya mulai dari bilangan bulat hingga angka floating-point, nilai
Infinity
dan
NaN
), nilai logis, string, nilai
undefined
dan
null
. Perhatikan bahwa ketika memeriksa
typeof null === 'object'
menghasilkan
true
,
null
adalah nilai primitif.
Nilai-nilai primitif tidak dapat diubah. Mereka tidak bisa diubah. Tentu saja, Anda dapat menulis sesuatu yang baru dalam variabel yang menyimpan nilai primitif. Misalnya, ini menulis nilai baru ke variabel
x
:
let x = 1; x++;
Tetapi pada saat yang sama, tidak ada perubahan (mutasi) dari nilai numerik primitif
1
.
Dalam beberapa bahasa, misalnya, dalam C, ada konsep melewati argumen fungsi dengan referensi dan nilai. JavaScript juga memiliki hal serupa. Bagaimana tepatnya pekerjaan dengan data diatur tergantung pada jenisnya. Jika nilai primitif yang diwakili oleh variabel tertentu diteruskan ke fungsi, dan kemudian diubah dalam fungsi ini, nilai yang disimpan dalam variabel asli tidak berubah. Namun, jika Anda meneruskan nilai objek yang diwakili oleh variabel ke fungsi dan memodifikasinya, maka apa yang disimpan dalam variabel ini juga akan berubah.
Perhatikan contoh berikut:
function primitiveMutator(val) { val = val + 1; } let x = 1; primitiveMutator(x); console.log(x);
Nilai-nilai primitif (dengan pengecualian
NaN
misterius, yang tidak sama dengan dirinya sendiri) selalu berubah menjadi sama dengan nilai-nilai primitif lainnya yang terlihat seperti diri mereka sendiri. Sebagai contoh:
const first = "abc" + "def"; const second = "ab" + "cd" + "ef"; console.log(first === second);
Namun, konstruksi nilai objek yang terlihat sama secara lahiriah tidak akan mengarah pada fakta bahwa entitas akan diperoleh, jika dibandingkan, kesetaraan mereka satu sama lain akan terungkap. Anda dapat memverifikasi ini dengan:
const obj1 = { name: "Intrinsic" }; const obj2 = { name: "Intrinsic" }; console.log(obj1 === obj2);
Objek memainkan peran mendasar dalam JavaScript. Mereka digunakan secara harfiah di mana-mana. Sebagai contoh, mereka sering digunakan dalam bentuk koleksi kunci / nilai. Tetapi sebelum munculnya tipe data
Symbol
, hanya string yang dapat digunakan sebagai kunci objek. Ini adalah batasan serius pada penggunaan benda-benda dalam bentuk koleksi. Saat mencoba menetapkan nilai non-string sebagai kunci objek, nilai ini dilemparkan ke string. Anda dapat memverifikasi ini dengan:
const obj = {}; obj.foo = 'foo'; obj['bar'] = 'bar'; obj[2] = 2; obj[{}] = 'someobj'; console.log(obj);
Ngomong-ngomong, meskipun ini membawa kita sedikit jauh dari topik karakter, saya ingin mencatat bahwa struktur data
Map
dibuat untuk memungkinkan penggunaan data kunci / nilai menyimpan dalam situasi di mana kunci bukan string.
Apa itu simbol?
Sekarang kami telah menemukan fitur nilai primitif dalam JavaScript, kami akhirnya siap untuk mulai berbicara tentang karakter. Simbol adalah makna primitif yang unik. Jika Anda mendekati simbol dari posisi ini, Anda akan melihat bahwa simbol dalam hal ini mirip dengan objek, karena penciptaan beberapa contoh simbol akan mengarah pada penciptaan nilai yang berbeda. Namun, simbol, adalah nilai-nilai primitif yang tidak berubah. Berikut ini adalah contoh bekerja dengan karakter:
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2);
Saat membuat turunan karakter, Anda dapat menggunakan argumen string pertama opsional. Argumen ini adalah deskripsi simbol yang dimaksudkan untuk digunakan dalam debugging. Nilai ini tidak mempengaruhi simbol itu sendiri.
const s1 = Symbol('debug'); const str = 'debug'; const s2 = Symbol('xxyy'); console.log(s1 === str);
Simbol sebagai kunci untuk properti objek
Simbol dapat digunakan sebagai kunci properti untuk objek. Ini sangat penting. Berikut ini adalah contoh menggunakannya:
const obj = {}; const sym = Symbol(); obj[sym] = 'foo'; obj.bar = 'bar'; console.log(obj);
Harap dicatat bahwa kunci yang ditentukan oleh karakter tidak dikembalikan ketika metode
Object.keys()
. Kode yang ditulis sebelum penampilan karakter di JS tidak tahu apa-apa tentang mereka, akibatnya, informasi tentang kunci objek yang diwakili oleh karakter tidak boleh dikembalikan oleh metode
Object.keys()
kuno.
Pada pandangan pertama, sepertinya fitur karakter di atas memungkinkan Anda untuk menggunakannya untuk membuat properti pribadi objek JS. Di banyak bahasa pemrograman lain, Anda bisa membuat properti objek tersembunyi menggunakan kelas. Kurangnya fitur ini telah lama dianggap sebagai salah satu kekurangan JavaScript.
Sayangnya, kode yang berfungsi dengan objek dapat dengan bebas mengakses kunci string mereka. Kode juga dapat mengakses kunci yang ditentukan oleh karakter, bahkan, bahkan jika kode dari mana mereka bekerja dengan objek tidak memiliki akses ke karakter yang sesuai. Misalnya, menggunakan metode
Reflect.ownKeys()
, Anda bisa mendapatkan daftar semua kunci objek, baik yang string maupun yang karakter:
function tryToAddPrivate(o) { o[Symbol('Pseudo Private')] = 42; } const obj = { prop: 'hello' }; tryToAddPrivate(obj); console.log(Reflect.ownKeys(obj));
Perhatikan bahwa pekerjaan saat ini sedang berlangsung untuk melengkapi kelas dengan kemampuan untuk menggunakan properti pribadi. Fitur ini disebut
Bidang Pribadi . Benar, itu tidak mempengaruhi sepenuhnya semua objek, merujuk hanya kepada mereka yang dibuat berdasarkan kelas yang disiapkan sebelumnya. Dukungan untuk bidang pribadi sudah tersedia di browser Chrome versi 72 dan lebih lama.
Mencegah tabrakan nama properti objek
Simbol, tentu saja, tidak menambah kemampuan JavaScript untuk membuat properti pribadi objek, tetapi mereka adalah inovasi berharga dalam bahasa karena alasan lain. Yaitu, mereka berguna dalam situasi ketika perpustakaan tertentu perlu menambahkan properti ke objek yang dijelaskan di luar mereka, dan pada saat yang sama tidak takut tabrakan nama-nama properti objek.
Pertimbangkan contoh di mana dua pustaka yang berbeda ingin menambahkan metadata ke objek. Mungkin kedua perpustakaan perlu melengkapi objek dengan beberapa pengidentifikasi. Jika Anda hanya menggunakan sesuatu seperti string
id
dari dua huruf untuk nama properti seperti itu, Anda mungkin menghadapi situasi di mana satu perpustakaan menimpa properti yang ditentukan oleh yang lain.
function lib1tag(obj) { obj.id = 42; } function lib2tag(obj) { obj.id = 369; }
Jika kita menggunakan simbol dalam contoh kita, maka setiap perpustakaan dapat menghasilkan, setelah inisialisasi, simbol yang dibutuhkan. Simbol-simbol ini kemudian dapat digunakan untuk menetapkan properti ke objek dan untuk mengakses properti ini.
const library1property = Symbol('lib1'); function lib1tag(obj) { obj[library1property] = 42; } const library2property = Symbol('lib2'); function lib2tag(obj) { obj[library2property] = 369; }
Dengan melihat skenario seperti itu Anda bisa mendapatkan keuntungan dari penampilan karakter dalam JavaScript.
Namun, mungkin ada pertanyaan tentang penggunaan perpustakaan untuk nama-nama properti objek, string acak atau string dengan struktur yang kompleks, termasuk, misalnya, nama perpustakaan. String serupa dapat membentuk sesuatu seperti ruang nama untuk pengidentifikasi yang digunakan oleh perpustakaan. Misalnya, mungkin terlihat seperti ini:
const library1property = uuid();
Secara umum, Anda bisa melakukannya. Pendekatan serupa, pada kenyataannya, sangat mirip dengan apa yang terjadi ketika menggunakan simbol. Dan jika, menggunakan pengidentifikasi acak atau ruang nama, beberapa perpustakaan tidak akan menghasilkan, secara kebetulan, nama properti yang sama, maka tidak akan ada masalah dengan nama.
Pembaca yang cerdik akan mengatakan sekarang bahwa dua pendekatan yang dipertimbangkan untuk menamai properti objek tidak sepenuhnya setara. Nama properti yang dihasilkan secara acak atau menggunakan ruang nama memiliki kelemahan: kunci yang sesuai sangat mudah ditemukan, terutama jika kode mencari kunci objek atau mengurutkannya. Perhatikan contoh berikut:
const library2property = 'LIB2-NAMESPACE-id';
Jika simbol digunakan untuk nama kunci dalam situasi ini, maka representasi JSON objek tidak akan mengandung nilai simbol. Kenapa begitu? Faktanya adalah fakta bahwa tipe data baru telah muncul dalam JavaScript tidak berarti bahwa perubahan telah dibuat untuk spesifikasi JSON. JSON mendukung, sebagai kunci properti, hanya string. Saat membuat serial objek, tidak ada upaya yang dilakukan untuk mewakili karakter dengan cara khusus apa pun.
Masalah yang dipertimbangkan untuk mendapatkan nama properti di representasi JSON objek dapat diselesaikan dengan menggunakan
Object.defineProperty()
:
const library2property = uuid();
Kunci string yang "disembunyikan" dengan mengatur
deskriptor enumerable
mereka ke perilaku
false
dalam cara yang sama seperti kunci yang diwakili oleh karakter. Keduanya tidak ditampilkan ketika
Object.keys()
dipanggil, dan keduanya dapat dideteksi menggunakan
Reflect.ownKeys()
. Begini tampilannya:
const obj = {}; obj[Symbol()] = 1; Object.defineProperty(obj, 'foo', { enumberable: false, value: 2 }); console.log(Object.keys(obj));
Di sini, saya harus mengatakan, kita hampir menciptakan kembali kemungkinan simbol, menggunakan cara lain dari JS. Secara khusus, kedua kunci yang diwakili oleh simbol dan kunci pribadi tidak termasuk dalam representasi JSON dari suatu objek. Keduanya dapat dikenali dengan merujuk pada metode
Reflect.ownKeys()
. Akibatnya, keduanya tidak dapat disebut benar-benar pribadi. Jika kami berasumsi bahwa beberapa nilai acak atau ruang nama pustaka digunakan untuk menghasilkan nama kunci, maka ini berarti bahwa kami menghilangkan risiko tabrakan nama.
Namun, ada satu perbedaan kecil antara menggunakan nama simbol dan nama yang dibuat menggunakan mekanisme lain. Karena string tidak dapat diubah, dan karakter dijamin unik, selalu ada kemungkinan seseorang, setelah melalui semua kemungkinan kombinasi karakter dalam string, akan menyebabkan benturan nama. Dari sudut pandang matematika, ini berarti bahwa karakter benar-benar memberi kita kesempatan berharga yang tidak dimiliki string.
Dalam Node.js, saat memeriksa objek (misalnya, menggunakan
console.log()
), jika metode objek yang disebut
inspect
terdeteksi, maka metode ini digunakan untuk mendapatkan representasi string objek dan kemudian menampilkannya di layar. Sangat mudah untuk memahami bahwa setiap orang tidak dapat memperhitungkan hal ini, oleh karena itu perilaku sistem tersebut dapat menyebabkan panggilan ke metode objek
inspect
, yang dirancang untuk menyelesaikan masalah yang tidak terkait dengan pembentukan representasi string dari objek. Fitur ini tidak digunakan lagi dalam Node.js 10, dalam versi 11 metode dengan nama yang sama diabaikan saja. Sekarang, untuk mengimplementasikan fitur ini,
require('util').inspect.custom
. Ini berarti bahwa tidak seorang pun akan pernah dapat mengganggu sistem secara tidak sengaja dengan membuat metode objek yang disebut
inspect
.
Imitasi properti pribadi
Berikut ini pendekatan menarik yang dapat Anda gunakan untuk mensimulasikan properti pribadi objek. Pendekatan ini melibatkan penggunaan fitur JavaScript modern lainnya - objek proxy. Objek tersebut berfungsi sebagai pembungkus untuk objek lain yang memungkinkan pemrogram untuk campur tangan dalam tindakan yang dilakukan dengan objek ini.
Objek proxy menawarkan banyak cara untuk mencegat tindakan yang dilakukan pada objek. Kami tertarik pada kemampuan untuk mengontrol operasi tombol membaca suatu objek. Kami tidak akan membahas detail tentang objek proxy di sini. Jika Anda tertarik, lihat publikasi
ini .
Kita bisa menggunakan proxy untuk mengontrol properti objek apa yang terlihat dari luar. Dalam hal ini, kami ingin membuat proxy yang menyembunyikan dua properti yang kami tahu. Satu memiliki nama string
_favColor
, dan yang kedua diwakili oleh karakter yang ditulis ke variabel
favBook
:
let proxy; { const favBook = Symbol('fav book'); const obj = { name: 'Thomas Hunter II', age: 32, _favColor: 'blue', [favBook]: 'Metro 2033', [Symbol('visible')]: 'foo' }; const handler = { ownKeys: (target) => { const reportedKeys = []; const actualKeys = Reflect.ownKeys(target); for (const key of actualKeys) { if (key === favBook || key === '_favColor') { continue; } reportedKeys.push(key); } return reportedKeys; } }; proxy = new Proxy(obj, handler); } console.log(Object.keys(proxy));
Berurusan dengan properti yang namanya diwakili oleh string
_favColor
tidak sulit: cukup baca kode sumber. Kunci dinamis (seperti tombol uuid yang kita lihat di atas) dapat dicocokkan dengan brute force. Tetapi tanpa referensi ke simbol, Anda tidak dapat mengakses nilai
Metro 2033
dari objek
proxy
.
Perlu dicatat bahwa di Node.js ada satu fitur yang melanggar privasi objek proxy. Fitur ini tidak ada dalam bahasa itu sendiri, jadi tidak relevan untuk runtime JS lainnya, seperti browser. Faktanya adalah bahwa fitur ini memungkinkan Anda untuk mengakses objek yang tersembunyi di belakang objek proxy, jika Anda memiliki akses ke objek proxy. Berikut adalah contoh yang menunjukkan kemampuan untuk mem-bypass mekanisme yang ditunjukkan dalam cuplikan kode sebelumnya:
const [originalObject] = process .binding('util') .getProxyDetails(proxy); const allKeys = Reflect.ownKeys(originalObject); console.log(allKeys[3]);
Sekarang, untuk mencegah penggunaan fitur ini dalam contoh spesifik Node.js, Anda harus memodifikasi objek global
Reflect
atau pengikatan proses
util
. Namun, ini adalah tugas lain. Jika Anda tertarik, lihat posting
ini tentang melindungi API berbasis JavaScript.
Ringkasan
Pada artikel ini, kami berbicara tentang tipe data
Symbol
, tentang fitur apa yang disediakan untuk pengembang JavaScript, dan tentang mekanisme bahasa apa yang dapat digunakan untuk mensimulasikan fitur ini.
Pembaca yang budiman! Apakah Anda menggunakan simbol dalam proyek JavaScript Anda?
