Properti "prototype"
banyak digunakan oleh inti JavaScript itu sendiri. Semua fungsi konstruktor bawaan menggunakannya.
Pertama kita akan melihat detailnya, lalu bagaimana menggunakannya untuk menambahkan kemampuan baru pada objek bawaan.
Katakanlah kita mengeluarkan objek kosong:
misalkan obj = {}; peringatan(keberatan); // "[Objek Objek]" ?
Di mana kode yang menghasilkan string "[object Object]"
? Itu adalah metode toString
bawaan, tapi di manakah itu? obj
kosong!
…Tetapi notasi singkatnya obj = {}
sama dengan obj = new Object()
, di mana Object
adalah fungsi konstruktor objek bawaan, dengan prototype
sendiri yang mereferensikan objek besar dengan toString
dan metode lainnya.
Inilah yang terjadi:
Ketika new Object()
dipanggil (atau objek literal {...}
dibuat), [[Prototype]]
-nya diatur ke Object.prototype
sesuai dengan aturan yang kita bahas di bab sebelumnya:
Jadi ketika obj.toString()
dipanggil metodenya diambil dari Object.prototype
.
Kita dapat memeriksanya seperti ini:
misalkan obj = {}; alert(obj.__proto__ === Objek.prototipe); // BENAR peringatan(obj.toString === obj.__proto__.toString); //BENAR alert(obj.toString === Objek.prototipe.toString); //BENAR
Harap dicatat bahwa tidak ada lagi [[Prototype]]
dalam rantai di atas Object.prototype
:
alert(Objek.prototipe.__proto__); // batal
Objek bawaan lainnya seperti Array
, Date
, Function
dan lainnya juga menyimpan metode dalam prototipe.
Misalnya, saat kita membuat array [1, 2, 3]
, konstruktor default new Array()
digunakan secara internal. Jadi Array.prototype
menjadi prototipenya dan menyediakan metode. Itu sangat hemat memori.
Berdasarkan spesifikasi, semua prototipe bawaan memiliki Object.prototype
di atas. Itu sebabnya sebagian orang mengatakan bahwa “segala sesuatu diwarisi dari benda”.
Berikut gambaran keseluruhannya (untuk 3 built-in yang sesuai):
Mari kita periksa prototipe secara manual:
misalkan arr = [1, 2, 3]; // mewarisi dari Array.prototype? waspada( arr.__proto__ === Array.prototipe ); // BENAR // lalu dari Object.prototype? alert( arr.__proto__.__proto__ === Objek.prototipe ); // BENAR // dan null di atas. peringatan( arr.__proto__.__proto__.__proto__ ); // batal
Beberapa metode dalam prototipe mungkin tumpang tindih, misalnya, Array.prototype
memiliki toString
sendiri yang mencantumkan elemen yang dipisahkan koma:
misalkan arr = [1, 2, 3] peringatan(arr); // 1,2,3 <-- hasil dari Array.prototype.toString
Seperti yang telah kita lihat sebelumnya, Object.prototype
juga memiliki toString
, tetapi Array.prototype
lebih dekat dalam rantainya, sehingga varian array digunakan.
Alat dalam browser seperti konsol pengembang Chrome juga menunjukkan warisan ( console.dir
mungkin perlu digunakan untuk objek bawaan):
Objek bawaan lainnya juga bekerja dengan cara yang sama. Fungsi genap – mereka adalah objek dari konstruktor Function
bawaan, dan metodenya ( call
/ apply
dan lainnya) diambil dari Function.prototype
. Fungsi juga memiliki toString
sendiri.
fungsi f() {} alert(f.__proto__ == Fungsi.prototipe); // BENAR alert(f.__proto__.__proto__ == Objek.prototipe); // benar, mewarisi dari objek
Hal yang paling rumit terjadi dengan string, angka, dan boolean.
Seingat kita, mereka bukanlah objek. Namun jika kita mencoba mengakses propertinya, objek pembungkus sementara dibuat menggunakan konstruktor bawaan String
, Number
dan Boolean
. Mereka menyediakan metodenya dan menghilang.
Objek-objek ini dibuat tanpa terlihat oleh kita dan sebagian besar mesin mengoptimalkannya, namun spesifikasinya menjelaskannya persis seperti ini. Metode objek ini juga berada dalam prototipe, tersedia sebagai String.prototype
, Number.prototype
dan Boolean.prototype
.
Nilai null
dan undefined
tidak memiliki pembungkus objek
Nilai khusus null
dan undefined
berdiri terpisah. Mereka tidak memiliki pembungkus objek, sehingga metode dan properti tidak tersedia untuk mereka. Dan tidak ada prototipe yang sesuai juga.
Prototipe asli dapat dimodifikasi. Misalnya, jika kita menambahkan metode ke String.prototype
, metode tersebut akan tersedia untuk semua string:
String.prototipe.tampilkan = fungsi() { waspada(ini); }; "LEDAKAN!".tampilkan(); // BOOM!
Selama proses pengembangan, kita mungkin mempunyai ide untuk metode bawaan baru yang ingin kita miliki, dan kita mungkin tergoda untuk menambahkannya ke prototipe asli. Tapi itu umumnya merupakan ide yang buruk.
Penting:
Prototipe bersifat global, sehingga mudah menimbulkan konflik. Jika dua perpustakaan menambahkan metode String.prototype.show
, salah satunya akan menimpa metode yang lain.
Jadi, secara umum, memodifikasi prototipe asli dianggap sebagai ide yang buruk.
Dalam pemrograman modern, hanya ada satu kasus di mana modifikasi prototipe asli disetujui. Itu polifilling.
Polyfilling adalah istilah untuk membuat pengganti suatu metode yang ada dalam spesifikasi JavaScript, namun belum didukung oleh mesin JavaScript tertentu.
Kami kemudian dapat mengimplementasikannya secara manual dan mengisi prototipe bawaan dengannya.
Misalnya:
if (!String.prototype.repeat) {// jika tidak ada metode seperti itu // tambahkan ke prototipe String.prototipe.ulangi = fungsi(n) { // ulangi string sebanyak n kali // sebenarnya, kodenya seharusnya sedikit lebih rumit dari itu // (algoritma lengkap ada di spesifikasi) // tetapi bahkan polyfill yang tidak sempurna sering kali dianggap cukup baik kembalikan Array baru(n + 1).join(ini); }; } peringatan("La".ulangi(3) ); // LaLaLa
Dalam bab Dekorator dan penerusan, panggilan/lamar kita berbicara tentang peminjaman metode.
Saat itulah kita mengambil metode dari satu objek dan menyalinnya ke objek lain.
Beberapa metode prototipe asli sering kali dipinjam.
Misalnya, jika kita membuat objek mirip array, kita mungkin ingin menyalin beberapa metode Array
ke objek tersebut.
Misalnya
misalkan obj = { 0: "Halo", 1: "dunia!", panjang: 2, }; obj.join = Array.prototipe.join; waspada( obj.join(',') ); // Halo, dunia!
Ini berfungsi karena algoritme internal metode join
bawaan hanya peduli pada indeks yang benar dan properti length
. Itu tidak memeriksa apakah objek tersebut memang sebuah array. Banyak metode bawaan yang seperti itu.
Kemungkinan lain adalah mewarisi dengan menyetel obj.__proto__
ke Array.prototype
, sehingga semua metode Array
tersedia secara otomatis di obj
.
Tapi itu tidak mungkin jika obj
sudah mewarisi dari objek lain. Ingat, kita hanya bisa mewarisi satu objek dalam satu waktu.
Metode peminjaman bersifat fleksibel, memungkinkan untuk menggabungkan fungsi dari objek yang berbeda jika diperlukan.
Semua objek bawaan mengikuti pola yang sama:
Metode disimpan dalam prototipe ( Array.prototype
, Object.prototype
, Date.prototype
, dll.)
Objek itu sendiri hanya menyimpan data (item array, properti objek, tanggal)
Primitif juga menyimpan metode dalam prototipe objek pembungkus: Number.prototype
, String.prototype
dan Boolean.prototype
. Hanya undefined
dan null
yang tidak memiliki objek pembungkus
Prototipe bawaan dapat dimodifikasi atau diisi dengan metode baru. Namun tidak disarankan untuk mengubahnya. Satu-satunya kasus yang diperbolehkan adalah ketika kita menambahkan standar baru, namun standar tersebut belum didukung oleh mesin JavaScript
pentingnya: 5
Tambahkan ke prototipe semua fungsi metode defer(ms)
, yang menjalankan fungsi setelah ms
milidetik.
Setelah Anda melakukannya, kode berikut akan berfungsi:
fungsi f() { peringatan("Halo!"); } f.menunda(1000); // menampilkan "Halo!" setelah 1 detik
Fungsi.prototipe.defer = fungsi(ms) { setTimeout(ini, ms); }; fungsi f() { peringatan("Halo!"); } f.menunda(1000); // menampilkan "Halo!" setelah 1 detik
pentingnya: 4
Tambahkan ke prototipe semua fungsi metode defer(ms)
, yang mengembalikan pembungkus, menunda panggilan sebanyak ms
milidetik.
Berikut ini contoh cara kerjanya:
fungsi f(a, b) { peringatan( a + b ); } f.menunda(1000)(1, 2); // menampilkan 3 setelah 1 detik
Harap dicatat bahwa argumen harus diteruskan ke fungsi aslinya.
Fungsi.prototipe.defer = fungsi(ms) { misalkan f = ini; fungsi pengembalian(...args) { setTimeout(() => f.apply(ini, args), ms); } }; // periksa fungsi f(a, b) { peringatan( a + b ); } f.menunda(1000)(1, 2); // menampilkan 3 setelah 1 detik
Harap dicatat: kami menggunakan this
di f.apply
untuk membuat dekorasi kami berfungsi untuk metode objek.
Jadi jika fungsi wrapper dipanggil sebagai metode objek, maka this
diteruskan ke metode asli f
.
Fungsi.prototipe.defer = fungsi(ms) { misalkan f = ini; fungsi pengembalian(...args) { setTimeout(() => f.apply(ini, args), ms); } }; biarkan pengguna = { nama: "Yohanes", ucapkan Hai() { alert(ini.nama); } } pengguna.sayHi = pengguna.sayHi.defer(1000); pengguna.sayHi();