
Siapa pun yang menggunakan JavaScript yang luar biasa untuk tujuan apa pun bertanya-tanya: mengapa typeof null "object" ? typeof dari fungsi mengembalikan "fungsi" , tetapi dari Array - "objek" ? dan di mana getClass di kelas kebanggaan Anda? Dan meskipun sebagian besar spesifikasi dan fakta sejarah menjawab dengan mudah dan alami, saya ingin menarik garis ... sedikit lebih banyak untuk saya sendiri.
Jika, pembaca, tipe dan contoh Anda tidak cukup untuk Anda dalam tugas Anda, dan Anda menginginkan beberapa spesifik, bukan "objek" , maka itu bisa berguna nanti. Oh ya, tentang bebek: mereka juga akan, hanya sedikit salah.
Latar Belakang Singkat
Andal mendapatkan jenis variabel dalam JavaScript selalu menjadi tugas yang tidak sepele, tentu saja tidak untuk pemula. Dalam kebanyakan kasus, tentu saja tidak diperlukan, hanya:
if (typeof value === 'object' && value !== null) {
dan sekarang Anda tidak Cannot read property of null
menangkap Cannot read property of null
- analog lokal NPE. Apakah itu familier?
Dan kemudian kami mulai menggunakan fungsi sebagai konstruktor semakin sering, dan terkadang berguna untuk memeriksa jenis objek yang dibuat dengan cara ini. Tetapi hanya menggunakan typeof dari instance tidak akan berfungsi, karena kita mendapatkan "objek" dengan benar.
Maka masih normal untuk menggunakan model OOP prototipe dalam JavaScript, ingat? Kami memiliki beberapa objek, dan melalui tautan ke prototipenya, kami dapat menemukan properti konstruktor yang menunjuk ke fungsi yang dengannya objek itu dibuat. Dan kemudian sedikit sihir dengan toString dari fungsi dan ekspresi reguler, dan ini dia hasilnya:
f.toString().match(/function\s+(\w+)(?=\s*\()/m)[1]
Kadang-kadang mereka mengajukan pertanyaan seperti itu di wawancara, tetapi mengapa?
Ya, kita hanya bisa menyimpan representasi string dari tipe sebagai properti khusus dan mendapatkannya dari objek melalui rantai prototipe:
function Type() {}; Type.prototype.typeName = 'Type'; var o = new Type; o.typeName; < "Type"
Hanya dua kali Anda harus menulis "Ketik" : dalam deklarasi fungsi dan di properti.
Untuk objek bawaan (seperti Array atau Tanggal ) kami memiliki properti rahasia [[Kelas]] , yang dapat dihubungkan melalui toString dari Objek standar:
Object.prototype.toString.call(new Array); < "[object Array]"
Sekarang kami memiliki kelas, dan tipe khusus akhirnya diperbaiki dalam bahasa: ini bukan LiveScript untuk Anda; kami menulis kode yang didukung dalam jumlah besar!
Sekitar waktu yang sama, Symbol.toStringTag dan Function.name muncul , yang dengannya kita dapat mengambil typeof kita dengan cara baru.
Secara umum, sebelum kita melanjutkan, saya ingin mencatat bahwa masalah yang sedang dipertimbangkan berkembang di StackOverflow bersama dengan bahasa dan naik dari editor ke editor: 9 tahun yang lalu , 7 tahun yang lalu , belum terlalu lama, atau ini dan itu .
Keadaan saat ini
Sebelumnya, kami sudah memeriksa secara detail Symbol.toStringTag dan Function.name . Singkatnya, karakter toStringTag internal adalah [[Kelas]] modern , hanya kita yang dapat mendefinisikannya kembali untuk objek kita. Dan properti Function.name dilegalkan di hampir semua browser dengan typeName yang sama dari contoh: mengembalikan nama fungsi.
Tanpa ragu, Anda dapat mendefinisikan fungsi seperti itu:
function getTag(any) { if (typeof any === 'object' && any !== null) { if (typeof any[Symbol.toStringTag] === 'string') { return any[Symbol.toStringTag]; } if (typeof any.constructor === 'function' && typeof any.constructor.name === 'string') { return any.constructor.name; } } return Object.prototype.toString.call(any).match(/\[object\s(\w+)]/)[1]; }
- JIKA variabel adalah objek, maka:
1.1. JIKA objek ditimpa ke StringTag , lalu kembalikan;
1.2. JIKA fungsi konstruktor dikenal untuk objek dan fungsi memiliki properti nama , lalu kembalikan; - AKHIRNYA TERJADI dengan menggunakan metode toString dari objek Object , yang akan melakukan semua pekerjaan polimorfik untuk kita untuk variabel variabel lain.
Objek dengan toStringTag :
let kitten = { [Symbol.toStringTag]: 'Kitten' }; getTag(kitten); < "Kitten"
Kelas dengan toStringTag :
class Cat { get [Symbol.toStringTag]() { return 'Kitten'; } } getTag(new Cat); < "Kitten"
Menggunakan constructor.name :
class Dog {} getTag(new Dog); < "Dog"
→ Lebih banyak contoh dapat ditemukan di repositori ini.
Dengan demikian, sekarang cukup mudah untuk menentukan jenis variabel apa pun dalam JavaScript. Fungsi ini memungkinkan Anda memeriksa variabel secara seragam untuk jenis dan menggunakan ekspresi sakelar sederhana alih-alih cek bebek dalam fungsi polimorfik. Secara umum, saya tidak pernah menyukai pendekatan berdasarkan mengetik bebek, mereka mengatakan jika sesuatu memiliki properti sambatan , apakah array atau sesuatu?
Bebek yang salah
Memahami jenis variabel dengan adanya metode atau properti tertentu tentu saja adalah urusan semua orang dan tergantung pada situasinya. Tapi saya akan mengambil getTag ini dan memeriksa beberapa hal bahasa.
Kelas?
"Bebek" favorit saya dalam JavaScript adalah kelas-kelasnya. Orang-orang yang mulai menulis JavaScript dengan ES-2015, terkadang tidak curiga apa kelas-kelas ini. Dan kebenarannya adalah:
class Person { constructor(name) { this.name = name; } hello() { return this.name; } } let user = new Person('John'); user.hello(); < "John"
Kami memiliki kata kunci kelas , konstruktor, beberapa metode, bahkan diperluas . Kami juga membuat turunan dari kelas ini melalui yang baru . Itu terlihat seperti kelas dalam arti biasa - itu artinya kelas!
Namun, ketika Anda mulai menambahkan metode baru secara real-time ke "kelas", dan pada saat yang sama mereka segera tersedia untuk instance yang sudah dibuat, beberapa hilang:
Person.prototype.hello = function() { return `Is not ${this.name}`; } user.hello(); < "Is not John"
Jangan lakukan ini!
Dan tidak dapat dipercaya bahwa ini hanyalah gula sintaksis atas model prototipe, karena secara konseptual tidak ada yang berubah dalam bahasa. Jika kita memanggil getTag dari Person , kita mendapatkan "Function" , bukan "Class" buatan, dan ini patut diingat.
Fungsi lainnya
Ada beberapa cara untuk mendeklarasikan fungsi dalam JavaScript: FunctionDeclaration , FunctionExpression, dan baru-baru ini ArrowFunction . Kita semua tahu kapan dan apa yang harus digunakan: semuanya sangat berbeda. Selain itu, jika kita memanggil getTag dari fungsi yang dideklarasikan oleh salah satu opsi yang diusulkan, kita mendapatkan "Function" .
Faktanya, ada banyak lagi cara untuk mendefinisikan suatu fungsi dalam suatu bahasa. Kami menambahkan ke daftar setidaknya ClassDeclaration dipertimbangkan, maka fungsi dan generator asynchronous, dll: AsyncFunctionDeclaration , AsyncFunctionExpression , AsyncArrowFunction , GeneratorDeclaration , GeneratorExpression , ClassExpression , MethodDefinition (daftar tidak lengkap). Dan sepertinya mereka berkata begitu apa? semua hal di atas berperilaku seperti fungsi - yang berarti getTag juga akan mengembalikan "Fungsi" . Tetapi ada satu fitur: semua opsi tentu fungsi, tetapi tidak semua langsung - Fungsi .
Ada subtipe fungsi bawaan :
Konstruktor fungsi dirancang agar subclassable.
Tidak ada cara sintaksis untuk membuat instance dari subkelas Function kecuali untuk subkelas GeneratorFunction dan AsyncFunction bawaan.
Kami memiliki Function dan di dalam GeneratorFunction dan AsyncFunction dengan konstruktornya "diwarisi" darinya. Ini menekankan bahwa asinks dan generator memiliki sifat uniknya sendiri. Dan sebagai hasilnya:
async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } getTag(sleep); < "AsyncFunction"
Pada saat yang sama, kami tidak dapat membuat fungsi seperti itu melalui operator baru , dan panggilannya mengembalikan Janji kepada kami:
getTag(sleep(100)); < "Promise"
Contoh dengan fungsi generator:
function* incg(i) { while(1) yield i += 1; } getTag(incg); < "GeneratorFunction"
Memanggil fungsi seperti itu mengembalikan sebuah instance - objek Generator :
let inc = incg(0); getTag(inc); < "Generator"
Simbol toStringTag cukup didefinisikan ulang untuk asinks dan generator. Tetapi typeof untuk fungsi apa pun akan menunjukkan "fungsi" .
Objek Inline
Kami memiliki hal-hal seperti Set , Peta , Tanggal atau Kesalahan . Menerapkan getTag kepada mereka akan mengembalikan "Function" , karena ini adalah fungsi - konstruktor dari koleksi, tanggal, dan kesalahan yang dapat diubah . Dari instance yang kita dapatkan masing-masing - "Set" , "Map" , "Date" dan " Error ".
Tapi hati-hati! masih ada objek seperti JSON atau Matematika . Jika Anda terburu-buru, Anda dapat mengasumsikan situasi yang sama. Tapi tidak! ini sangat berbeda - objek tunggal bawaan. Mereka tidak instantiable ( is not a constructor
). Panggilan typeof akan mengembalikan "objek" (yang akan meragukannya). Tetapi getTag akan beralih ke toStringTag dan mendapatkan "JSON" dan "Math" . Ini adalah pengamatan terakhir yang ingin saya bagikan.
Ayo pergi tanpa fanatisme
Saya bertanya sedikit lebih dalam dari biasanya tentang jenis variabel dalam JavaScript baru-baru ini ketika saya memutuskan untuk menulis inspektur objek sederhana saya untuk analisis kode dinamis (bermain-main). Materi tersebut tidak hanya diterbitkan dalam pemrograman Abnormal , karena Anda tidak memerlukannya dalam produksi: ada typeof , instanceof , Array.isArray , isNaN , dan semua hal lain yang perlu diingat ketika melakukan verifikasi yang diperlukan. Sebagian besar proyek memiliki TypeScript, Dart, atau Flow. Saya suka JavaScript !