Selamat siang teman!
Simbol adalah tipe data primitif yang diperkenalkan di ECMAScript2015 (ES6) yang memungkinkan Anda membuat pengenal unik: const uniqueKey = Symbol ('SymbolName').
Anda dapat menggunakan simbol sebagai kunci untuk properti objek. Simbol yang ditangani JavaScript dengan cara khusus disebut Simbol Terkenal . Karakter ini digunakan oleh algoritme JavaScript bawaan. Misalnya, Symbol.iterator digunakan untuk mengulang elemen array, string. Ini juga dapat digunakan untuk menentukan fungsi iterator Anda sendiri.
Simbol-simbol ini memainkan peran penting karena memungkinkan Anda untuk menyesuaikan perilaku objek.
Menjadi unik, menggunakan simbol sebagai kunci objek (bukan string) memudahkan untuk menambahkan fungsionalitas baru ke objek. Pada saat yang sama, tidak perlu khawatir tentang benturan antar kunci (karena setiap karakter itu unik), yang bisa menjadi masalah saat menggunakan string.
Artikel ini akan fokus pada simbol terkenal dengan contoh penggunaannya.
Demi kesederhanaan, sintaks dari Simbol yang terkenal. Simbol <nama> ada dalam format @@ <nama>. Misalnya, Symbol.iterator direpresentasikan sebagai @@ iterator, Symbol.toPrimitive sebagai @@ toPrimitive, dll.
Jika kita mengatakan bahwa suatu objek memiliki metode @@ iterator, maka objek tersebut berisi properti yang disebut Symbol.iterator, yang diwakili oleh fungsi: {[Symbol.iterator]: function () {}}.
1. Pengenalan singkat tentang simbol
Karakter adalah tipe primitif (seperti angka, string, atau boolean), unik dan tidak dapat diubah (tidak dapat diubah).
Untuk membuat simbol, panggil fungsi Symbol () dengan argumen opsional - nama atau, lebih tepatnya, deskripsi simbol:
const mySymbol = Symbol()
const namedSymbol = Symbol('myName')
typeof mySymbol // symbol
typeof namedSymbol // symbol
mySymbol dan namesSymbol adalah simbol primitif. nameSymbol bernama 'myName', yang biasanya digunakan untuk kode debug.
Setiap panggilan ke Symbol () membuat simbol unik baru. Dua karakter itu unik (atau khusus) meskipun memiliki nama yang sama:
const first = Symbol()
const second = Symbol()
first === second // false
const firstNamed = Symbol('Lorem')
const secondNamed = Symbol('Lorem')
firstNamed === secondNamed // false
Simbol bisa menjadi kunci objek. Untuk melakukan ini, Anda harus menggunakan sintaks properti yang dihitung ([simbol]) dalam literal objek atau definisi kelas:
const strSymbol = Symbol('String')
const myObj = {
num: 1,
[strSymbol]: 'Hello World'
}
myObj[strSymbol] // Hello World
Object.getOwnPropertyNames(myObj) // ['num']
Object.getOwnPropertySymbols(myObj) // [Symbol(String)]
Properti simbol tidak bisa diambil menggunakan Object.keys () atau Object.getOwnPropertyNames (). Untuk mengaksesnya, Anda perlu menggunakan fungsi khusus Object.getOwnPropertySymbols ().
Menggunakan simbol terkenal sebagai kunci dapat mengubah perilaku objek.
Simbol yang terkenal tersedia sebagai properti yang tidak dapat dihitung, tidak dapat diubah, dan tidak dapat dikonfigurasi dari objek Simbol. Untuk mendapatkannya, gunakan notasi titik: Symbol.iterator, Symbol.hasInstance, dll.
Berikut cara mendapatkan daftar simbol yang terkenal:
Object.getOwnPropertyNames(Symbol)
// ["hasInstance", "isConcatSpreadable", "iterator", "toPrimitive",
// "toStringTag", "unscopables", "match", "replace", "search",
// "split", "species", ...]
typeof Symbol.iterator // symbol
Object.getOwnPropertyNames (Symbol) mengembalikan daftar properti asli dari objek Symbol, termasuk simbol yang terkenal. Symbol.iterator adalah simbol tipe, tentunya.
2. @@ iterator, yang memungkinkan Anda membuat objek menjadi iterable (iterable)
Symbol.iterator mungkin adalah simbol yang paling terkenal. Ini memungkinkan Anda untuk menentukan bagaimana sebuah objek harus diiterasi menggunakan pernyataan for-of atau operator penyebaran (dan apakah itu harus diulangi sama sekali).
Banyak tipe bawaan seperti string, array, peta, set, atau set dapat diulang secara default karena mereka memiliki metode @@ iterator:
const myStr = 'Hi'
typeof myStr[Symbol.iterator] // function
for (const char of myStr) {
console.log(char) // : 'H', 'i'
}
[...myStr] // ['H', 'i']
Variabel myStr berisi string primitif yang memiliki properti Symbol.iterator. Properti ini berisi fungsi yang digunakan untuk mengulang karakter dalam string.
Objek di mana metode Symbol.iterator didefinisikan harus sesuai dengan protokol iterasi (iterator) . Lebih tepatnya, metode ini harus mengembalikan objek yang sesuai dengan protokol yang ditentukan. Objek seperti itu harus memiliki metode next () yang mengembalikan {value: <iterator_value>, done: <boolean_finished_iterator>}.
Dalam contoh berikut, kita membuat objek myMethods iterable yang memungkinkan kita melakukan iterasi atas metodenya:
function methodsIterator() {
let index = 0
const methods = Object.keys(this)
.filter(key => typeof this[key] === 'function')
return {
next: () => ({
done: index === methods.length,
value: methods[index++]
})
}
}
const myMethods = {
toString: () => '[object myMethods]',
sum: (a, b) => a + b,
numbers: [1, 3, 5],
[Symbol.iterator]: methodsIterator
}
for (const method of myMethods) {
console.log(method) // toString, sum
}
methodIterator () adalah fungsi yang mengembalikan iterator {next: function () {}}. Objek myMethods mendefinisikan properti komputasi [Symbol.iterator] dengan nilai methodIterator. Ini membuat objek dapat diulang menggunakan for-of loop. Metode objek juga bisa diperoleh dengan menggunakan [... myMethods]. Objek seperti itu dapat diubah menjadi array menggunakan Array.from (myMethods).
Pembuatan objek iterable dapat disederhanakan dengan menggunakan fungsi generator . Fungsi ini mengembalikan objek Generator yang sesuai dengan protokol iterasi.
Mari buat kelas Fibonacci dengan metode @@ iterator yang menghasilkan urutan bilangan Fibonacci:
class Fibonacci {
constructor(n) {
this.n = n
}
*[Symbol.iterator]() {
let a = 0, b = 1, index = 0
while (index < this.n) {
index++
let current = a
a = b
b = current + a
yield current
}
}
}
const sequence = new Fibonacci(6)
const numbers = [...sequence]
console.log(numbers) // [0, 1, 1, 2, 3, 5]
* [Symbol.iterator] () {} mendefinisikan metode kelas - fungsi generator. Instans Fibonacci sesuai dengan protokol brute-force. Operator penyebaran memanggil metode @@ iterator untuk membuat deretan angka.
Jika tipe atau objek primitif berisi @@ iterator, itu bisa digunakan dalam skenario berikut:
- Perulangan melalui elemen dengan for-of
- Membuat array elemen menggunakan operator spread
- Membuat array menggunakan Array.from (iterableObject)
- Dalam ekspresi hasil * untuk diteruskan ke generator lain
- Dalam konstruktor Map (), WeakMap (), Set () dan WeakSet ()
- Dalam metode statis Promise.all (), Promise.race (), dll.
Anda dapat membaca selengkapnya tentang membuat objek yang dapat berulang di sini .
3. @@ hasInstance untuk mengkonfigurasi instanceof
Secara default, instance obj dari operator pembuat memeriksa untuk melihat apakah rantai prototipe obj berisi objek Constructor.prototype. Mari pertimbangkan sebuah contoh:
function Constructor() {
// ...
}
const obj = new Constructor()
const objProto = Object.getPrototypeOf(obj)
objProto === Constructor.prototype // true
obj instanceof Constructor // true
obj instanceof Object // true
Object instanceof Constructor mengembalikan nilai true karena prototipe obj adalah Constructor.prototype (sebagai hasil dari memanggil konstruktor). instanceof merujuk ke rantai prototipe sesuai kebutuhan, jadi instance object dari Object juga mengembalikan true.
Terkadang aplikasi membutuhkan pemeriksaan contoh yang lebih ketat.
Untungnya, kami memiliki kemampuan untuk mendefinisikan metode @@ hasInstance untuk mengubah perilaku instanceof. Object instanceof Type sama dengan Type [Symbol.hasInstance] (obj).
Mari kita periksa apakah variabelnya dapat diulang:
class Iterable {
static [Symbol.hasInstance](obj) {
return typeof obj[Symbol.iterator] === 'function'
}
}
const arr = [1, 3, 5]
const str = 'Hi'
const num = 21
arr instanceof Iterable // true
str instanceof Iterable // true
num instanceof Iterable // false
Kelas Iterable berisi metode statis @@ hasInstance. Metode ini memeriksa apakah obj dapat diulang, yaitu. apakah itu berisi properti Symbol.iterator. arr dan str adalah iterable, tetapi num tidak.
4. @@ toPrimitive untuk mengubah objek menjadi primitif
Gunakan Symbol.toPrimitive untuk mendefinisikan properti yang nilainya merupakan objek untuk fungsi konversi primitif. @@ toPrimitive mengambil satu parameter, petunjuk, yang bisa berupa angka, string, atau default. petunjuk menunjukkan jenis nilai yang dikembalikan.
Mari tingkatkan transformasi array:
function arrayToPrimitive(hint) {
if (hint === 'number') {
return this.reduce((x, y) => x + y)
} else if (hint === 'string') {
return `[${this.join(', ')}]`
} else {
// hint
return this.toString()
}
}
const array = [1, 3, 5]
array[Symbol.toPrimitive] = arrayToPrimitive
// . hint
+ array // 9
// . hint
`array is ${array}` // array is [1, 3, 5]
// . hint default
'array elements: ' + array // array elements: 1,3,5
arrayToPrimitive (hint) adalah fungsi yang mengubah array menjadi primitif berdasarkan nilai hint. Menyetel array [Symbol.toPrimitive] ke arrayToPrimitive memaksa array untuk menggunakan metode transformasi baru. Melakukan + panggilan array @@ toPrimitive dengan nilai petunjuk angka. Jumlah elemen array dikembalikan. array adalah $ {array} panggilan @@ toPrimitive dengan hint = string. Array diubah menjadi string '[1, 3, 5]'. Akhirnya 'elemen array:' + array menggunakan petunjuk = default untuk mengubah. Array diubah menjadi '1,3,5'.
Metode @@ toPrimitive digunakan untuk merepresentasikan objek sebagai tipe primitif:
- Saat menggunakan operator persamaan longgar (abstrak): object == primitif
- Saat menggunakan operator penjumlahan / penggabungan: object + primitive
- Saat menggunakan operator pengurangan: object - primitive
- Dalam berbagai situasi, mengonversi objek menjadi primitif: String (objek), Angka (objek), dll.
5. @@ toStringTag untuk membuat deskripsi objek standar
Gunakan Symbol.toStringTag untuk mendefinisikan properti yang nilainya berupa string yang mendeskripsikan tipe objek. Metode @@ toStringTag digunakan oleh Object.prototype.toString ().
Spec mendefinisikan nilai default yang dikembalikan oleh Object.prototype.toString () untuk banyak tipe:
const toString = Object.prototype.toString
toString.call(undefined) // [object Undefined]
toString.call(null) // [object Null]
toString.call([1, 4]) // [object Array]
toString.call('Hello') // [object String]
toString.call(15) // [object Number]
toString.call(true) // [object Boolean]
// Function, Arguments, Error, Date, RegExp ..
toString.call({}) // [object Object]
Tipe ini tidak memiliki properti Symbol.toStringTag karena algoritma Object.prototype.toString () mengevaluasinya dengan cara khusus.
Properti yang dimaksud didefinisikan dalam tipe seperti simbol, fungsi generator, kartu, janji, dll. Perhatikan contoh:
const toString = Object.prototype.toString
const noop = function() { }
Symbol.iterator[Symbol.toStringTag] // Symbol
(function* () {})[Symbol.toStringTag] // GeneratorFunction
new Map()[Symbol.toStringTag] // Map
new Promise(noop)[Symbol.toStringTag] // Promise
toString.call(Symbol.iterator) // [object Symbol]
toString.call(function* () {}) // [object GeneratorFunction]
toString.call(new Map()) // [object Map]
toString.call(new Promise(noop)) // [object Promise]
Jika objek bukan dari grup tipe standar dan tidak berisi properti @@ toStringTag, Objek dikembalikan. Tentu saja, kami dapat mengubah ini:
const toString = Object.prototype.toString
class SimpleClass { }
toString.call(new SimpleClass) // [object Object]
class MyTypeClass {
constructor() {
this[Symbol.toStringTag] = 'MyType'
}
}
toString.call(new MyTypeClass) // [object MyType]
Sebuah instance dari kelas SimpleClass tidak memiliki properti @@ toStringTag, jadi Object.prototype.toString () mengembalikan [object Object]. Konstruktor kelas MyTypeClass menetapkan properti @@ toStringTag ke instance dengan nilai MyType, sehingga Object.prototype.toString () mengembalikan [object MyType].
Perhatikan bahwa @@ toStringTag diperkenalkan untuk kompatibilitas ke belakang. Penggunaannya tidak diinginkan. Lebih baik menggunakan instanceof (bersama dengan @@ hasInstance) atau typeof untuk menentukan jenis objek.
6. @@ spesies untuk membuat objek turunan
Gunakan Symbol.species untuk mendefinisikan properti yang nilainya adalah fungsi konstruktor yang digunakan untuk membuat objek turunan.
Nilai @@ spesies dari banyak konstruktor adalah konstruktornya sendiri:
Array[Symbol.species] === Array // true
Map[Symbol.species] === Map // true
RegExp[Symbol.species] === RegExp // true
Pertama, perhatikan bahwa objek turunan adalah objek yang dikembalikan setelah melakukan operasi tertentu pada objek aslinya. Misalnya, memanggil map () mengembalikan objek turunan - hasil dari transformasi elemen array.
Biasanya, objek turunan mengacu pada konstruktor yang sama dengan objek aslinya. Namun terkadang perlu untuk menentukan konstruktor lain (mungkin salah satu kelas standar): di sinilah @@ spesies dapat membantu.
Misalkan kita memperluas konstruktor Array dengan kelas anak MyArray untuk menambahkan beberapa metode yang berguna. Dalam melakukannya, kami ingin konstruktor objek turunan dari instance MyArray menjadi Array. Untuk melakukan ini, Anda perlu menentukan properti terhitung @@ spesies dengan nilai Array:
class MyArray extends Array {
isEmpty() {
return this.length === 0
}
static get [Symbol.species]() {
return Array
}
}
const array = new MyArray(2, 3, 5)
array.isEmpty() // false
const odds = array.filter(item => item % 2 === 1)
odds instanceof Array // true
odds instanceof MyArray // false
MyArray mendefinisikan properti komputasi statis Symbol.species. Ini menentukan bahwa konstruktor untuk objek turunan haruslah konstruktor Array. Nanti saat memfilter elemen array, array.filter () mengembalikan Array.
Properti @@ spesies dihitung digunakan oleh array dan metode array yang diketik seperti map (), concat (), slice (), splice (), yang mengembalikan objek turunan. Menggunakan properti ini dapat berguna untuk memperluas peta, ekspresi reguler, atau janji sambil mempertahankan konstruktor aslinya.
7. Buat ekspresi reguler dalam bentuk objek: @@ match, @@ replace, @@ search dan @@ split
Prototipe string berisi 4 metode yang menggunakan ekspresi reguler sebagai argumen:
- String.prototype.match (regExp)
- String.prototype.replace (regExp, newSubstr)
- String.prototype.search (regExp)
- String.prototype.split (regExp, limit)
ES6 memungkinkan metode ini untuk menerima tipe lain selama properti komputasi terkait ditentukan: @@ match, @@ replace, @@ search, dan @@ split.
Anehnya, prototipe RegExp berisi metode yang ditentukan, juga ditentukan menggunakan simbol:
typeof RegExp.prototype[Symbol.match] // function
typeof RegExp.prototype[Symbol.replace] // function
typeof RegExp.prototype[Symbol.search] // function
typeof RegExp.prototype[Symbol.split] // function
Dalam contoh berikut, kami mendefinisikan kelas yang dapat digunakan sebagai pengganti ekspresi reguler:
class Expression {
constructor(pattern) {
this.pattern = pattern
}
[Symbol.match](str) {
return str.includes(this.pattern)
}
[Symbol.replace](str, replace) {
return str.split(this.pattern).join(replace)
}
[Symbol.search](str) {
return str.indexOf(this.pattern)
}
[Symbol.split](str) {
return str.split(this.pattern)
}
}
const sunExp = new Expression('')
' '.match(sunExp) // true
' '.match(sunExp) // false
' day'.replace(sunExp, '') // ' '
' '.search(sunExp) // 8
''.split(sunExp) // ['', '']
Kelas Expression mendefinisikan metode @@ match, @@ replace, @@ search, dan @@ split. Kemudian sebuah instance dari kelas ini - sunExp digunakan dalam metode yang sesuai, bukan ekspresi reguler.
8. @@ isConcatSpreadable untuk mengkonversi objek ke array
Symbol.isConcatSpreadable adalah nilai Boolean yang menunjukkan bahwa objek dapat diubah menjadi array menggunakan metode Array.prototype.concat ().
Secara default, metode concat () mengambil elemen dari array (mendekomposisi array menjadi elemen-elemen yang ada di dalamnya) saat menggabungkan array:
const letters = ['a', 'b']
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters) // ['c', 'd', 'e', 'a', 'b']
Untuk menggabungkan dua larik, berikan huruf sebagai argumen ke metode concat (). Elemen larik huruf menjadi bagian dari hasil penggabungan: ['c', 'd', 'e', 'a', 'b'].
Untuk mencegah larik didekomposisi menjadi elemen dan menjadikan larik sebagai bagian dari hasil gabungan sebagaimana adanya, properti @@ isConcatSpreadable harus disetel ke false:
const letters = ['a', 'b']
letters[Symbol.isConcatSpreadable] = false
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters) // ['c', 'd', 'e', ['a', 'b']]
Berbeda dengan array, metode concat () tidak mendekomposisi objek seperti array menjadi elemen. Perilaku ini juga dapat diubah dengan @@ isConcatSpreadable:
const letters = { 0: 'a', 1: 'b', length: 2 }
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters)
// ['c', 'd', 'e', {0: 'a', 1: 'b', length: 2}]
letters[Symbol.isConcatSpreadable] = true
otherLetters.concat('e', letters) // ['c', 'd', 'e', 'a', 'b']
9. @@ unscopables untuk mengakses properti dengan
Symbol.unscopables adalah properti komputasi yang nama aslinya dikecualikan dari objek yang ditambahkan ke awal rantai cakupan menggunakan pernyataan with. Properti @@ unscopables memiliki format berikut: {propertyName: <boolean_exclude_binding>}.
ES6 mendefinisikan @@ unscopables hanya untuk array. Ini dilakukan untuk menyembunyikan metode baru yang dapat menimpa variabel dengan nama yang sama di kode lama:
Array.prototype[Symbol.unscopables]
// { copyWithin: true, entries: true, fill: true,
// find: true, findIndex: true, keys: true }
let numbers = [1, 3, 5]
with (numbers) {
concat(7) // [1, 3, 5, 7]
entries // ReferenceError: entries is not defined
}
Kita bisa mengakses metode concat () di dalam tubuh dengan, karena metode ini tidak terdapat dalam properti @@ unscopables. Metode entries () ditentukan dalam properti ini dan disetel ke true, yang membuatnya tidak tersedia di dalam.
@@ unscopables diperkenalkan semata-mata untuk kompatibilitas dengan kode lama menggunakan pernyataan with (tidak digunakan lagi dan tidak diizinkan dalam mode ketat).
Saya harap Anda menemukan sesuatu yang menarik untuk diri Anda sendiri. Terima kasih atas perhatian Anda.