JavaScript adalah bahasa yang berorientasi objek. Ada pepatah yang sangat klasik dalam JavaScript: semuanya adalah objek. Karena berorientasi objek, ada tiga karakteristik utama berorientasi objek: enkapsulasi, warisan, dan polimorfisme. Di sini kita berbicara tentang warisan JavaScript, dan kita akan berbicara tentang dua lainnya nanti.
Warisan JavaScript tidak sama dengan C ++. Warisan C ++ didasarkan pada kelas, sedangkan warisan JavaScript didasarkan pada prototipe.
Sekarang masalahnya ada di sini.
Apa prototipe itu? Untuk prototipe, kita dapat merujuk ke kelas di C ++ dan menyimpan properti dan metode objek. Misalnya, mari kita tulis objek sederhana
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
this.name = name;
}
Animal.prototype.setName = function (name) {
this.name = name;
}
var hewan = hewan baru ("wangwang");
Kita dapat melihat bahwa ini adalah hewan objek, yang memiliki nama atribut dan metode setName. Perhatikan bahwa setelah prototipe dimodifikasi, seperti menambahkan metode, semua contoh objek akan berbagi metode ini. Misalnya
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
this.name = name;
}
var hewan = hewan baru ("wangwang");
Pada saat ini, hewan hanya memiliki atribut nama. Jika kami menambahkan kalimat,
Salinan kode adalah sebagai berikut:
Animal.prototype.setName = function (name) {
this.name = name;
}
Pada saat ini, hewan juga akan memiliki metode setName.
Salinan Warisan - Mulai dari objek kosong, kita tahu bahwa di antara tipe dasar JS, ada jenis yang disebut objek, dan contohnya yang paling mendasar adalah objek kosong, yaitu, contoh yang dihasilkan dengan memanggil objek baru () secara langsung, atau dinyatakan dengan literal {}. Objek kosong adalah "objek bersih", dengan hanya sifat dan metode yang telah ditentukan, sementara semua objek lain diwarisi dari objek kosong, sehingga semua objek memiliki sifat dan metode yang telah ditentukan ini. Prototipe sebenarnya adalah instance objek. Arti prototipe adalah: jika konstruktor memiliki objek prototipe A, instance yang dibuat oleh konstruktor harus disalin dari A. karena instance disalin dari objek A, instance harus mewarisi semua sifat, metode dan sifat lain dari A. Jadi, bagaimana replikasi diterapkan? Metode 1: Bangun Salin Setiap instance yang dibangun disalin dari prototipe, dan instance baru menempati ruang memori yang sama dengan prototipe. Meskipun ini membuat OBJ1 dan OBJ2 "sepenuhnya konsisten" dengan prototipe mereka, itu juga sangat tidak ekonomis - konsumsi ruang memori akan meningkat dengan cepat. Seperti yang ditunjukkan pada gambar:
Metode 2: Salin saat menulis strategi ini berasal dari teknologi sistem penipuan yang konsisten: Salin pada penulisan. Contoh khas dari penipuan semacam ini adalah Dynamic Link Library (DDL) dalam sistem operasi, yang area memorinya selalu disalin pada penulisan. Seperti yang ditunjukkan pada gambar:
Kita hanya perlu menentukan dalam sistem bahwa OBJ1 dan OBJ2 setara dengan prototipe mereka, jadi ketika membaca, kita hanya perlu mengikuti instruksi untuk membaca prototipe. Ketika kita perlu menulis properti suatu objek (seperti OBJ2), kita menyalin gambar prototipe dan membuat operasi selanjutnya menunjuk ke gambar. Seperti yang ditunjukkan pada gambar:
Keuntungan dari metode ini adalah bahwa kita tidak membutuhkan banyak overhead memori saat membuat instance dan membaca atribut. Kami hanya menggunakan beberapa kode untuk mengalokasikan memori saat menulis pertama kali, dan membawa beberapa kode dan overhead memori. Tetapi tidak akan ada overhead seperti itu sejak itu, karena efisiensi mengakses gambar dan mengakses prototipe konsisten. Namun, untuk sistem yang sering menulis operasi, metode ini tidak ekonomis dari metode sebelumnya. Metode 3: Membaca Traversal Metode ini mengubah granularitas salinan dari prototipe ke anggota. Metode ini ditandai dengan menyalin informasi anggota ke dalam gambar instan hanya ketika menulis anggota sebuah instance. Saat menulis properti objek, misalnya (OBJ2.Value = 10), nilai atribut bernama nilai akan dihasilkan dan ditempatkan dalam daftar anggota objek OBJ2. Lihat gambarnya:
Dapat ditemukan bahwa OBJ2 masih menjadi referensi ke prototipe, dan tidak ada contoh objek dengan ukuran yang sama dengan prototipe yang dibuat selama operasi. Dengan cara ini, operasi tulis tidak mengarah pada sejumlah besar alokasi memori, sehingga penggunaan memori tampak ekonomis. Perbedaannya adalah bahwa OBJ2 (dan semua instance objek) perlu mempertahankan daftar anggota. Daftar anggota ini mengikuti dua aturan: Dijamin diakses terlebih dahulu saat dibaca. Jika tidak ada atribut yang ditentukan dalam objek, cobalah untuk melintasi seluruh rantai prototipe objek sampai prototipe kosong atau properti ditemukan. Rantai prototipe akan dibahas nanti. Jelas, di antara tiga metode, baca traversal adalah kinerja terbaik. Oleh karena itu, warisan prototipe JavaScript adalah traversal yang dibaca. Orang -orang konstruktor yang akrab dengan C ++ pasti akan bingung setelah membaca kode objek teratas. Bagaimanapun, mudah dimengerti tanpa kata kunci kelas, ada kata kunci fungsi, tetapi kata kunci berbeda. Tapi bagaimana dengan konstruktor? Bahkan, JavaScript juga memiliki konstruktor yang serupa, tetapi mereka disebut konstruktor. Saat menggunakan operator baru, konstruktor telah dipanggil dan ini terikat sebagai objek. Misalnya, kami menggunakan kode berikut
Salinan kode adalah sebagai berikut:
var hewan = hewan ("wangwang");
Hewan tidak akan ditentukan. Beberapa orang akan mengatakan bahwa tidak ada nilai pengembalian tentu saja tidak ditentukan. Maka jika Anda mengubah definisi objek hewan:
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
this.name = name;
kembalikan ini;
}
Tebak hewan apa itu sekarang?
Saat ini, hewan telah menjadi jendela. Perbedaannya adalah bahwa ia memperluas jendela, sehingga jendela memiliki atribut nama. Ini karena default ke jendela ini, yaitu variabel tingkat atas tanpa menentukannya. Hanya dengan menelepon kata kunci baru yang dapat dipanggil dengan benar. Jadi, bagaimana menghindari kata kunci baru yang terlewatkan oleh orang yang menggunakannya? Kita bisa membuat beberapa perubahan kecil:
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
if (! (Contoh hewan ini)) {
mengembalikan hewan baru (nama);
}
this.name = name;
}
Ini akan sangat mudah. Konstruktor juga memiliki penggunaan lain, menunjukkan objek yang termasuk dalam instance. Kita dapat menggunakan instanceof untuk menilai, tetapi instanceof akan kembali ke objek leluhur dan objek nyata saat mewarisi, sehingga tidak terlalu cocok. Ketika konstruktor disebut baru, itu menunjuk ke objek saat ini secara default.
Salinan kode adalah sebagai berikut:
console.log (animal.prototype.constructor === hewan); // BENAR
Kita dapat berpikir secara berbeda: prototipe tidak berharga di awal fungsi, dan implementasinya mungkin merupakan logika berikut
// Set __proto__ adalah anggota built-in dari fungsi, get_prototyoe () adalah metodenya
Salinan kode adalah sebagai berikut:
var __proto__ = null;
function get_prototype () {
if (! __ proto__) {
__proto__ = objek baru ();
__proto __. Konstruktor = ini;
}
return __proto__;
}
Manfaat ini adalah menghindari membuat instance objek untuk setiap fungsi yang dinyatakan, menyimpan overhead. Konstruktor dapat dimodifikasi, yang akan dibahas nanti. Saya percaya semua orang tahu warisan apa yang didasarkan pada prototipe, sehingga mereka tidak memamerkan batas bawah IQ mereka.
Ada beberapa jenis warisan JS, berikut adalah dua jenis
1. Metode 1 Metode ini paling umum digunakan dan memiliki keamanan yang lebih baik. Mari kita tentukan dua objek terlebih dahulu
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
this.name = name;
}
function dog (usia) {
this.age = usia;
}
var dog = anjing baru (2);
Sangat sederhana untuk membangun warisan, arahkan prototipe objek anak ke instance objek induk (perhatikan bahwa itu adalah instance, bukan objek)
Salinan kode adalah sebagai berikut:
Dog.prototype = hewan baru ("wangwang");
Pada saat ini, anjing akan memiliki dua atribut, nama dan usia. Dan jika contoh operator digunakan untuk anjing
Salinan kode adalah sebagai berikut:
console.log (instance anjing dari hewan); // BENAR
console.log (instance anjing dari anjing); // PALSU
Ini mencapai warisan, tetapi ada masalah kecil
Salinan kode adalah sebagai berikut:
console.log (dog.prototype.constructor === hewan); // BENAR
console.log (dog.prototype.constructor === dog); // PALSU
Anda dapat melihat bahwa objek yang ditunjuk oleh konstruktor telah berubah, yang tidak memenuhi tujuan kami. Kami tidak dapat menentukan siapa instance yang baru kami miliki. Karena itu, kami dapat menambahkan satu kalimat:
Salinan kode adalah sebagai berikut:
Dog.prototype.constructor = dog;
Mari kita lihat lagi:
Salinan kode adalah sebagai berikut:
console.log (instance anjing dari hewan); // PALSU
console.log (instance anjing dari anjing); // BENAR
Selesai. Metode ini adalah tautan dalam pemeliharaan rantai prototipe, yang akan dijelaskan secara rinci di bawah ini. 2. Metode 2 Metode ini memiliki manfaat dan kerugiannya, tetapi kerugiannya lebih besar daripada manfaatnya. Lihat kode terlebih dahulu
Salinan kode adalah sebagai berikut:
<pre name = "code"> function hewan (name) {
this.name = name;
}
Animal.prototype.setName = function (name) {
this.name = name;
}
function dog (usia) {
this.age = usia;
}
Dog.prototype = animal.prototype;
Ini memungkinkan penyalinan prototipe.
Keuntungan dari metode ini adalah tidak memerlukan instantiasi objek (dibandingkan dengan metode 1), menghemat sumber daya. Kerugiannya juga jelas. Selain masalah yang sama seperti di atas, yaitu, konstruktor menunjuk ke objek induk, ia hanya dapat menyalin properti dan metode yang dinyatakan oleh objek induk dengan prototipe. Dengan kata lain, dalam kode di atas, atribut nama objek hewan tidak dapat disalin, tetapi metode setName dapat disalin. Hal yang paling fatal adalah bahwa setiap modifikasi pada prototipe objek anak akan mempengaruhi prototipe objek induk, yaitu, contoh yang dinyatakan oleh kedua objek akan terpengaruh. Oleh karena itu, metode ini tidak disarankan.
Rantai prototipe
Siapa pun yang telah menulis tentang warisan tahu bahwa warisan dapat diwarisi dari berbagai tingkatan. Dan di JS, ini membentuk rantai prototipe. Artikel di atas juga menyebutkan rantai prototipe berkali -kali, jadi, apa rantai prototipe? Contoh setidaknya harus memiliki atribut proto yang menunjuk ke prototipe, yang merupakan dasar dari sistem objek dalam JavaScript. Namun, properti ini tidak terlihat, dan kami menyebutnya "rantai prototipe internal" untuk membedakannya dari "rantai prototipe konstruktor" yang terdiri dari prototipe konstruktor (yaitu, apa yang biasanya kita sebut "rantai prototipe"). Pertama -tama mari kita buat hubungan warisan sederhana sesuai dengan kode di atas:
Salinan kode adalah sebagai berikut:
fungsi hewan (nama) {
this.name = name;
}
function dog (usia) {
this.age = usia;
}
var hewan = hewan baru ("wangwang");
Dog.prototype = hewani;
var dog = anjing baru (2);
Sebagai pengingat, seperti yang disebutkan sebelumnya, semua objek mewarisi objek kosong. Jadi, kami membangun rantai prototipe:
Kita dapat melihat bahwa prototipe objek anak menunjuk ke contoh objek induk, membentuk rantai prototipe konstruktor. Objek proto bagian dalam instance anak juga merupakan instance yang menunjuk pada objek induk, membentuk rantai prototipe internal. Ketika kita perlu menemukan properti, kodenya mirip dengan
Salinan kode adalah sebagai berikut:
Fungsi getAttrFromObj (attr, obj) {
if (typeof (obj) === "objek") {
var proto = obj;
while (proto) {
if (proto.hasownproperty (attr)) {
return proto [attr];
}
Proto = Proto .__ Proto__;
}
}
kembali tidak ditentukan;
}
Dalam contoh ini, jika kita mencari atribut nama di anjing, itu akan dicari dalam daftar anggota di anjing. Tentu saja, itu tidak akan ditemukan, karena sekarang daftar anggota anjing hanyalah usia item. Maka itu akan terus mencari di sepanjang rantai prototipe, yaitu contoh yang ditunjuk oleh .proto, yaitu, pada hewan, temukan atribut nama dan kembalikan. Jika pencarian adalah properti yang tidak ada, ketika tidak dapat ditemukan pada hewan, itu akan terus mencari dengan .proTo, temukan objek kosong, dan kemudian terus mencari dengan .proTo, dan .proto dari objek kosong menunjuk ke nol, mencari keluar.
Pemeliharaan Rantai Prototipe Kami mengajukan pertanyaan ketika kami berbicara tentang prototipe warisan sekarang. Saat menggunakan Metode 10 untuk membangun warisan, konstruktor instance objek anak menunjuk ke objek induk. Keuntungan dari ini adalah bahwa kita dapat mengakses rantai prototipe melalui atribut konstruktor, dan kerugiannya juga jelas. Objek yang contohnya harus menunjuk pada dirinya sendiri, yaitu.
Salinan kode adalah sebagai berikut:
(Obj baru ()). Prototype.Constructor === OBJ;
Kemudian, setelah kami menulis ulang properti prototipe, konstruktor instance yang dihasilkan oleh objek anak tidak menunjuk pada dirinya sendiri! Ini bertentangan dengan niat asli konstruktor. Kami menyebutkan solusi di atas:
Salinan kode adalah sebagai berikut:
Dog.prototype = hewan baru ("wangwang");
Dog.prototype.constructor = dog;
Sepertinya tidak ada yang salah. Tetapi pada kenyataannya, ini memunculkan masalah baru karena kami akan menemukan bahwa kami tidak dapat kembali ke rantai prototipe karena kami tidak dapat menemukan objek induk, dan atribut .Proto dari rantai prototipe internal tidak dapat diakses. Oleh karena itu, Spidermonkey memberikan peningkatan: tambahkan properti bernama __proto__ ke objek yang dibuat, yang selalu menunjuk pada prototipe yang digunakan oleh konstruktor. Dengan cara ini, setiap modifikasi konstruktor tidak akan mempengaruhi nilai __proto__, membuatnya lebih mudah untuk mempertahankan konstruktor.
Namun, ada dua masalah lagi:
__proto__ dapat ditulis ulang, yang berarti masih ada risiko saat menggunakannya
__proto__ adalah pemrosesan khusus spidermonkey dan tidak dapat digunakan di mesin lain (seperti JScript).
Ada cara lain bagi kita untuk mempertahankan sifat konstruktor dari prototipe dan menginisialisasi sifat konstruktor dari instance dalam fungsi konstruktor subkelas.
Kode ini adalah sebagai berikut: Tulis ulang objek anak
Salinan kode adalah sebagai berikut:
function dog (usia) {
this.constructor = arguments.callee;
this.age = usia;
}
Dog.prototype = hewan baru ("wangwang");
Dengan cara ini, konstruktor semua contoh objek anak dengan benar menunjuk ke objek, sedangkan konstruktor prototipe menunjuk ke objek induk. Meskipun metode ini relatif tidak efisien karena atribut konstruktor harus ditulis ulang setiap kali contoh dibangun, tidak ada keraguan bahwa metode ini dapat secara efektif menyelesaikan kontradiksi sebelumnya. ES5 memperhitungkan ini dan sepenuhnya memecahkan masalah ini: objek.getPrototypeOf () dapat digunakan kapan saja untuk mendapatkan prototipe nyata suatu objek tanpa mengakses konstruktor atau mempertahankan rantai prototipe eksternal. Oleh karena itu, sebagaimana disebutkan di bagian sebelumnya, kita dapat menulis ulang sebagai berikut:
Salinan kode adalah sebagai berikut:
Fungsi getAttrFromObj (attr, obj) {
if (typeof (obj) === "objek") {
Mengerjakan {
var proto = objek.getPrototypeOf (anjing);
if (proto [attr]) {
return proto [attr];
}
}
sementara (proto);
}
kembali tidak ditentukan;
}
Tentu saja, metode ini hanya dapat digunakan di browser yang mendukung ES5. Agar kompatibilitas mundur, kita masih perlu mempertimbangkan metode sebelumnya. Metode yang lebih cocok adalah mengintegrasikan dan merangkum kedua metode ini. Saya percaya pembaca sangat pandai dalam hal ini, jadi saya tidak akan keburukan di sini.