Pada bab pertama bagian ini, kami menyebutkan bahwa ada metode modern untuk menyiapkan prototipe.
Menyetel atau membaca prototipe dengan obj.__proto__
dianggap ketinggalan jaman dan agak ketinggalan jaman (dipindahkan ke apa yang disebut “Lampiran B” dari standar JavaScript, yang dimaksudkan hanya untuk browser).
Metode modern untuk mendapatkan/mengatur prototipe adalah:
Object.getPrototypeOf(obj) – mengembalikan [[Prototype]]
dari obj
.
Object.setPrototypeOf(obj, proto) – menyetel [[Prototype]]
dari obj
ke proto
.
Satu-satunya penggunaan __proto__
, yang tidak disukai, adalah sebagai properti saat membuat objek baru: { __proto__: ... }
.
Meskipun demikian, ada metode khusus untuk ini juga:
Object.create(proto[, descriptors]) – membuat objek kosong dengan proto
tertentu sebagai [[Prototype]]
dan deskriptor properti opsional.
Misalnya:
biarkan hewan = { makan: benar }; // membuat objek baru dengan hewan sebagai prototipenya biarkan kelinci = Objek.buat(hewan); // sama seperti {__proto__: hewan} alert(kelinci.makan); // BENAR alert(Object.getPrototypeOf(kelinci) === hewan); // BENAR Objek.setPrototypeOf(kelinci, {}); // ubah prototipe kelinci menjadi {}
Metode Object.create
sedikit lebih kuat, karena memiliki argumen opsional kedua: deskriptor properti.
Kita dapat memberikan properti tambahan pada objek baru di sana, seperti ini:
biarkan hewan = { makan: benar }; biarkan kelinci = Objek.buat(hewan, { melompat: { nilai: benar } }); alert(kelinci.melompat); // BENAR
Deskriptornya memiliki format yang sama seperti yang dijelaskan dalam bab Tanda dan deskriptor properti.
Kita dapat menggunakan Object.create
untuk melakukan kloning objek yang lebih kuat daripada menyalin properti di for..in
:
biarkan clone = Objek.buat( Objek.getPrototypeOf(obj), Objek.getOwnPropertyDescriptors(obj) );
Panggilan ini membuat salinan yang benar-benar tepat dari obj
, termasuk semua properti: properti data enumerable dan non-enumerable dan setter/getter – semuanya, dan dengan [[Prototype]]
yang tepat.
Ada banyak cara untuk mengelola [[Prototype]]
. Bagaimana hal itu bisa terjadi? Mengapa?
Itu karena alasan historis.
Warisan prototipe sudah ada dalam bahasa tersebut sejak awal mulanya, namun cara mengelolanya berkembang seiring berjalannya waktu.
Properti prototype
fungsi konstruktor telah berfungsi sejak zaman kuno. Ini adalah cara tertua untuk membuat objek dengan prototipe tertentu.
Kemudian, pada tahun 2012, Object.create
muncul dalam standar. Ini memberikan kemampuan untuk membuat objek dengan prototipe tertentu, tetapi tidak memberikan kemampuan untuk mendapatkan/mengaturnya. Beberapa browser menerapkan pengakses __proto__
non-standar yang memungkinkan pengguna mendapatkan/mengatur prototipe kapan saja, untuk memberikan lebih banyak fleksibilitas kepada pengembang.
Kemudian, pada tahun 2015, Object.setPrototypeOf
dan Object.getPrototypeOf
ditambahkan ke standar, untuk menjalankan fungsi yang sama seperti __proto__
. Karena __proto__
secara de-facto diterapkan di mana-mana, hal ini sudah tidak digunakan lagi dan dimasukkan ke dalam Lampiran B standar, yaitu: opsional untuk lingkungan non-browser.
Kemudian, pada tahun 2022, secara resmi diizinkan untuk menggunakan __proto__
dalam literal objek {...}
(dipindahkan dari Lampiran B), tetapi tidak sebagai pengambil/penyetel obj.__proto__
(masih dalam Lampiran B).
Mengapa __proto__
digantikan oleh fungsi getPrototypeOf/setPrototypeOf
?
Mengapa __proto__
direhabilitasi sebagian dan penggunaannya diizinkan di {...}
, tetapi tidak sebagai pengambil/penyetel?
Itu pertanyaan yang menarik, mengharuskan kita memahami mengapa __proto__
buruk.
Dan segera kita akan mendapatkan jawabannya.
Jangan ubah [[Prototype]]
pada objek yang sudah ada jika kecepatan penting
Secara teknis, kita bisa mendapatkan/mengatur [[Prototype]]
kapan saja. Namun biasanya kita hanya menyetelnya satu kali pada waktu pembuatan objek dan tidak mengubahnya lagi: rabbit
mewarisi dari animal
, dan itu tidak akan berubah.
Dan mesin JavaScript sangat dioptimalkan untuk ini. Mengubah prototipe “on-the-fly” dengan Object.setPrototypeOf
atau obj.__proto__=
adalah operasi yang sangat lambat karena merusak optimasi internal untuk operasi akses properti objek. Jadi hindarilah kecuali Anda tahu apa yang Anda lakukan, atau kecepatan JavaScript sama sekali tidak penting bagi Anda.
Seperti yang kita ketahui, objek dapat digunakan sebagai array asosiatif untuk menyimpan pasangan kunci/nilai.
…Tetapi jika kita mencoba menyimpan kunci yang disediakan pengguna di dalamnya (misalnya, kamus yang dimasukkan pengguna), kita dapat melihat kesalahan yang menarik: semua kunci berfungsi dengan baik kecuali "__proto__"
.
Lihat contohnya:
misalkan obj = {}; let key = prompt("Apa kuncinya?", "__proto__"); obj[kunci] = "beberapa nilai"; peringatan(obj[kunci]); // [objek Objek], bukan "nilai tertentu"!
Di sini, jika pengguna mengetik __proto__
, tugas di baris 4 diabaikan!
Hal ini mungkin mengejutkan bagi non-pengembang, tetapi cukup dapat dimengerti oleh kami. Properti __proto__
bersifat khusus: harus berupa objek atau null
. Sebuah string tidak bisa menjadi prototipe. Itu sebabnya penugasan string ke __proto__
diabaikan.
Tapi kami tidak bermaksud menerapkan perilaku seperti itu, bukan? Kami ingin menyimpan pasangan kunci/nilai, dan kunci bernama "__proto__"
tidak disimpan dengan benar. Jadi itu bug!
Di sini konsekuensinya tidak buruk. Namun dalam kasus lain kita mungkin menyimpan objek alih-alih string di obj
, dan kemudian prototipenya akan diubah. Akibatnya, eksekusi akan berjalan salah dengan cara yang sama sekali tidak terduga.
Parahnya – biasanya pengembang tidak memikirkan kemungkinan seperti itu sama sekali. Hal ini membuat bug tersebut sulit untuk diperhatikan dan bahkan mengubahnya menjadi kerentanan, terutama ketika JavaScript digunakan di sisi server.
Hal tak terduga juga mungkin terjadi saat menugaskan ke obj.toString
, karena ini merupakan metode objek bawaan.
Bagaimana kita bisa menghindari masalah ini?
Pertama, kita bisa beralih menggunakan Map
untuk penyimpanan alih-alih objek biasa, lalu semuanya baik-baik saja:
biarkan peta = Peta baru(); let key = prompt("Apa kuncinya?", "__proto__"); map.set(kunci, "nilai tertentu"); alert(map.get(kunci)); // "beberapa nilai" (sebagaimana dimaksud)
…Tetapi sintaksis Object
seringkali lebih menarik, karena lebih ringkas.
Untungnya, kita bisa menggunakan objek, karena pencipta bahasa sudah memikirkan masalah itu sejak lama.
Seperti yang kita ketahui, __proto__
bukanlah properti suatu objek, melainkan properti pengakses dari Object.prototype
:
Jadi, jika obj.__proto__
dibaca atau disetel, pengambil/penyetel terkait dipanggil dari prototipenya, dan ia mendapat/mengatur [[Prototype]]
.
Seperti yang dikatakan di awal bagian tutorial ini: __proto__
adalah cara untuk mengakses [[Prototype]]
, ini bukan [[Prototype]]
itu sendiri.
Sekarang, jika kita ingin menggunakan suatu objek sebagai array asosiatif dan bebas dari masalah seperti itu, kita dapat melakukannya dengan sedikit trik:
biarkan obj = Objek.buat(null); // atau: obj = { __proto__: null } let key = prompt("Apa kuncinya?", "__proto__"); obj[kunci] = "beberapa nilai"; peringatan(obj[kunci]); // "beberapa nilai"
Object.create(null)
membuat objek kosong tanpa prototipe ( [[Prototype]]
adalah null
):
Jadi, tidak ada pengambil/penyetel yang diwariskan untuk __proto__
. Sekarang diproses sebagai properti data biasa, jadi contoh di atas berfungsi dengan baik.
Kita dapat menyebut objek tersebut sebagai objek “sangat sederhana” atau “kamus murni”, karena objek tersebut bahkan lebih sederhana daripada objek biasa biasa {...}
.
Kelemahannya adalah objek tersebut tidak memiliki metode objek bawaan, misalnya toString
:
biarkan obj = Objek.buat(null); peringatan(keberatan); // Kesalahan (tidak ada toString)
…Tapi itu biasanya bagus untuk array asosiatif.
Perhatikan bahwa sebagian besar metode yang berhubungan dengan objek adalah Object.something(...)
, seperti Object.keys(obj)
– metode tersebut tidak ada dalam prototipe, sehingga metode tersebut akan tetap bekerja pada objek seperti:
biarkan chineseDictionary = Objek.buat(null); chineseDictionary.hello = "你好"; chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // halo, sampai jumpa
Untuk membuat objek dengan prototipe yang diberikan, gunakan:
Object.create
menyediakan cara mudah untuk menyalin objek secara dangkal dengan semua deskriptor:
biarkan clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
sintaks literal: { __proto__: ... }
, memungkinkan untuk menentukan beberapa properti
atau Object.create(proto[, descriptors]), memungkinkan untuk menentukan deskriptor properti.
Metode modern untuk mendapatkan/mengatur prototipe adalah:
Object.getPrototypeOf(obj) – mengembalikan [[Prototype]]
dari obj
(sama seperti pengambil __proto__
).
Object.setPrototypeOf(obj, proto) – menyetel [[Prototype]]
dari obj
ke proto
(sama seperti penyetel __proto__
).
Mendapatkan/mengatur prototipe menggunakan pengambil/penyetel __proto__
bawaan tidak disarankan, sekarang ada di Lampiran B spesifikasi.
Kita juga membahas objek tanpa prototipe, yang dibuat dengan Object.create(null)
atau {__proto__: null}
.
Objek-objek ini digunakan sebagai kamus, untuk menyimpan kunci apa pun (yang mungkin dibuat oleh pengguna).
Biasanya, objek mewarisi metode bawaan dan __proto__
pengambil/penyetel dari Object.prototype
, membuat kunci terkait “dihuni” dan berpotensi menyebabkan efek samping. Dengan prototipe null
, objek benar-benar kosong.
pentingnya: 5
Ada dictionary
objek, dibuat sebagai Object.create(null)
, untuk menyimpan pasangan key/value
apa pun.
Tambahkan metode dictionary.toString()
ke dalamnya, yang akan mengembalikan daftar kunci yang dibatasi koma. toString
Anda tidak akan muncul di for..in
di atas objek.
Begini cara kerjanya:
biarkan kamus = Objek.buat(null); // kode Anda untuk menambahkan metode kamus.toString // menambahkan beberapa data kamus.apple = "Apple"; kamus.__proto__ = "tes"; // __proto__ adalah kunci properti biasa di sini // hanya apple dan __proto__ yang ada dalam loop untuk(biarkan kunci dalam kamus) { peringatan(kunci); // "apel", lalu "__proto__" } // toString Anda sedang beraksi peringatan(kamus); // "apel,__proto__"
Metode ini dapat mengambil semua kunci enumerable menggunakan Object.keys
dan menampilkan daftarnya.
Untuk membuat toString
tidak dapat dihitung, mari kita definisikan menggunakan deskriptor properti. Sintaks Object.create
memungkinkan kita menyediakan objek dengan deskriptor properti sebagai argumen kedua.
biarkan kamus = Objek.buat(null, { toString: {// tentukan properti toString nilai() {//nilainya adalah sebuah fungsi return Objek.kunci(ini).join(); } } }); kamus.apple = "Apple"; kamus.__proto__ = "tes"; // apple dan __proto__ ada dalam loop untuk(biarkan kunci dalam kamus) { peringatan(kunci); // "apel", lalu "__proto__" } // daftar properti yang dipisahkan koma menurut toString peringatan(kamus); // "apel,__proto__"
Saat kita membuat properti menggunakan deskriptor, tandanya false
secara default. Jadi pada kode di atas, dictionary.toString
tidak dapat dihitung.
Lihat bab Tanda dan deskriptor properti untuk ditinjau.
pentingnya: 5
Mari buat objek rabbit
baru:
fungsi Kelinci(nama) { ini.nama = nama; } Kelinci.prototipe.sayHi = function() { alert(ini.nama); }; biarkan kelinci = kelinci baru("Kelinci");
Panggilan ini melakukan hal yang sama atau tidak?
kelinci.sayHi(); Kelinci.prototipe.sayHi(); Objek.getPrototypeOf(kelinci).sayHi(); kelinci.__proto__.sayHi();
Panggilan pertama memiliki this == rabbit
, panggilan lainnya memiliki this
sama dengan Rabbit.prototype
, karena sebenarnya itu adalah objek sebelum titik.
Jadi hanya panggilan pertama yang menunjukkan Rabbit
, panggilan lainnya menunjukkan undefined
:
fungsi Kelinci(nama) { ini.nama = nama; } Kelinci.prototipe.sayHi = function() { alert( ini.nama ); } biarkan kelinci = kelinci baru("Kelinci"); kelinci.sayHi(); // Kelinci Kelinci.prototipe.sayHi(); // belum diartikan Objek.getPrototypeOf(kelinci).sayHi(); // belum diartikan kelinci.__proto__.sayHi(); // belum diartikan