Dalam arti sebenarnya, JavaScript bukan bahasa yang berorientasi objek dan tidak memberikan metode warisan tradisional, tetapi memberikan cara pewarisan prototipe, menggunakan properti prototipe yang disediakan untuk mencapai pewarisan.
Prototipe dan rantai prototipe
Sebelum berbicara tentang prototipe warisan, kita harus berbicara tentang prototipe dan rantai prototipe terlebih dahulu. Bagaimanapun, ini adalah dasar untuk mewujudkan prototipe warisan.
Dalam JavaScript, setiap fungsi memiliki prototipe atribut prototipe yang menunjuk ke prototipe sendiri, dan objek yang dibuat oleh fungsi ini juga memiliki atribut __proTo__ yang menunjuk pada prototipe ini. Prototipe fungsi adalah objek, sehingga objek ini juga akan memiliki __proto__ yang menunjuk ke prototipe sendiri, sehingga ia lebih dalam lapisan demi lapisan sampai prototipe objek objek, sehingga membentuk rantai prototipe. Gambar berikut menjelaskan hubungan antara prototipe dan rantai prototipe dalam JavaScript dengan sangat baik.
Setiap fungsi adalah objek yang dibuat oleh fungsi fungsi, sehingga setiap fungsi juga memiliki atribut __proto__ yang menunjuk ke prototipe fungsi fungsi. Harus ditunjukkan di sini bahwa apa yang benar -benar membentuk rantai prototipe adalah atribut __proto__ dari masing -masing objek, bukan atribut prototipe fungsi, yang sangat penting.
Warisan prototipe
Mode Dasar
Salinan kode adalah sebagai berikut:
var parent = function () {
this.name = 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function () {
this.name = 'child';
};
Child.prototype = new Parent ();
var parent = new parent ();
var anak = anak baru ();
console.log (parent.getName ()); //induk
console.log (child.getname ()); //anak
Ini adalah cara termudah untuk mengimplementasikan prototipe warisan, secara langsung menetapkan objek kelas induk ke prototipe konstruktor subkelas, sehingga objek subkelas dapat mengakses properti di kelas induk dan prototipe konstruktor kelas induk. Diagram warisan prototipe dari metode ini adalah sebagai berikut:
Keuntungan dari metode ini jelas, implementasinya sangat sederhana dan tidak memerlukan operasi khusus; Kerugiannya juga jelas. Jika subkelas perlu melakukan tindakan inisialisasi yang sama seperti pada konstruktor kelas induk, maka Anda harus mengulangi operasi di kelas induk dalam konstruktor subkelas:
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
this.name = name || 'anak' ;
};
Child.prototype = new Parent ();
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getname ()); // mychild
Dalam situasi di atas, hanya membutuhkan atribut nama untuk diinisialisasi. Jika pekerjaan inisialisasi terus meningkat, metode ini sangat merepotkan. Oleh karena itu, ada cara untuk meningkatkan yang berikut.
Pinjam konstruktor
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ini, argumen);
};
Child.prototype = new Parent ();
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getname ()); // mychild
Metode di atas melakukan pekerjaan inisialisasi yang sama dengan menerapkan panggilan ke konstruktor kelas induk dalam konstruktor subkelas, sehingga tidak peduli berapa banyak pekerjaan inisialisasi yang dilakukan di kelas induk, subkelas dapat melakukan pekerjaan inisialisasi yang sama. Namun, ada masalah lain dengan implementasi di atas. Konstruktor kelas induk dieksekusi dua kali, sekali dalam konstruktor subkelas, dan sekali dalam prototipe subclass, ini banyak yang berlebihan, jadi kita perlu melakukan perbaikan:
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ini, argumen);
};
Child.prototype = parent.prototype;
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getname ()); // mychild
Dengan cara ini, kita hanya perlu menjalankan konstruktor kelas induk sekali di konstruktor subkelas, dan pada saat yang sama kita dapat mewarisi properti dalam prototipe kelas induk. Ini lebih sesuai dengan niat asli prototipe, yaitu untuk menempatkan konten yang perlu digunakan kembali dalam prototipe, dan kami hanya mewarisi konten yang dapat digunakan kembali dalam prototipe. Diagram prototipe dari metode di atas adalah sebagai berikut:
Mode Konstruktor Sementara (Mode Holy Grail)
Masih ada masalah dengan versi yang meminjam pola konstruktor di atas. Ini secara langsung menetapkan prototipe kelas induk ke prototipe subclass, yang akan menyebabkan masalah, yaitu, jika prototipe subclass dimodifikasi, modifikasi juga akan mempengaruhi prototipe kelas induk, dan kemudian mempengaruhi objek kelas induk. Ini jelas bukan yang diharapkan semua orang. Untuk mengatasi masalah ini, pola konstruktor sementara tersedia.
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ini, argumen);
};
var f = fungsi baru () {};
F.prototype = parent.prototype;
Child.prototype = baru f ();
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getname ()); // mychild
Diagram warisan prototipe dari metode ini adalah sebagai berikut:
Sangat mudah untuk melihat bahwa dengan menambahkan konstruktor sementara antara prototipe kelas induk dan prototipe kelas anak, koneksi antara prototipe kelas anak dan prototipe kelas induk terputus, sehingga prototipe kelas induk tidak akan terpengaruh ketika prototipe kelas anak dimodifikasi.
Metode saya
Mode Holy Grail berakhir dalam "Mode JavaScript", tetapi tidak peduli metode mana pun di atas, ada masalah yang tidak mudah ditemukan. Anda dapat melihat bahwa saya menambahkan atribut literal objek OBJ ke properti prototipe 'induk', tetapi itu tidak pernah berguna. Mari kita lihat situasi berikut berdasarkan mode Holy Grail:
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ini, argumen);
};
var f = fungsi baru () {};
F.prototype = parent.prototype;
Child.prototype = baru f ();
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
Dalam kasus di atas, ketika saya memodifikasi objek anak OBJ.A, OBJ.A dalam prototipe kelas induk juga akan dimodifikasi, yang akan menyebabkan masalah yang sama dengan prototipe bersama. Ini terjadi karena ketika mengakses Child.obj.a, kami akan mengikuti rantai prototipe dan menemukan prototipe kelas induk, kemudian menemukan atribut OBJ, dan kemudian memodifikasi OBJ.A. Mari kita lihat situasi berikut:
Salinan kode adalah sebagai berikut:
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ini, argumen);
};
var f = fungsi baru () {};
F.prototype = parent.prototype;
Child.prototype = baru f ();
var induk = induk baru ('myparent');
var anak = anak baru ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
Ada masalah utama di sini. Ketika suatu objek mengakses properti dalam prototipe, properti dalam prototipe hanya dibaca ke objek. Dengan kata lain, objek anak dapat membaca objek OBJ, tetapi referensi objek OBJ dalam prototipe tidak dapat dimodifikasi. Oleh karena itu, ketika anak memodifikasi OBJ, itu tidak akan mempengaruhi OBJ dalam prototipe. Ini hanya menambahkan atribut OBJ ke objeknya sendiri, menimpa atribut OBJ dalam prototipe induk. Ketika objek anak memodifikasi OBJ.A, pertama -tama membaca referensi untuk OBJ dalam prototipe. Pada saat ini, anak -anak. Metode warisan $ scope scope di angularjs diimplementasikan dengan pemodelan prototipe warisan di javasript.
Menurut deskripsi di atas, selama prototipe yang diakses dalam objek subkelas sama dengan prototipe kelas induk, situasi di atas akan terjadi. Oleh karena itu, kami dapat menyalin prototipe kelas induk dan kemudian menetapkannya ke prototipe subclass. Dengan cara ini, ketika subclass memodifikasi properti dalam prototipe, itu hanya memodifikasi salinan prototipe kelas induk, dan tidak akan mempengaruhi prototipe kelas induk. Implementasi spesifik adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
var deepclone = fungsi (sumber, target) {
Sumber = Sumber || {};
var tostr = object.prototype.tostring,
arrstr = '[objek array]';
untuk (var i di sumber) {
if (source.hasownproperty (i)) {
var item = sumber [i];
if (typeof item === 'objek') {
Target [i] = (ToStr.Apply (item) .TolowerCase () === arrstr): []? {};
DeepClone (item, target [i]);
}kalau tidak{
DeepClone (item, target [i]);
}
}
}
target pengembalian;
};
var parent = function (name) {
this.name = name || 'Parent';
};
Parent.prototype.getName = function () {
kembalikan nama ini;
};
Parent.prototype.obj = {a: '1'};
var child = function (name) {
Parent.Apply (ini, argumen);
};
Child.Prototype = DeepClone (Parent.Prototype);
var anak = anak baru ('anak');
var induk = induk baru ('induk');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = '2';
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 1
Berdasarkan semua pertimbangan di atas, implementasi spesifik dari warisan JavaScript adalah sebagai berikut. Hanya ketika anak dan orang tua fungsi dipertimbangkan:
Salinan kode adalah sebagai berikut:
var deepclone = fungsi (sumber, target) {
Sumber = Sumber || {};
var tostr = object.prototype.tostring,
arrstr = '[objek array]';
untuk (var i di sumber) {
if (source.hasownproperty (i)) {
var item = sumber [i];
if (typeof item === 'objek') {
Target [i] = (ToStr.Apply (item) .TolowerCase () === arrstr): []? {};
DeepClone (item, target [i]);
}kalau tidak{
DeepClone (item, target [i]);
}
}
}
target pengembalian;
};
var extand = fungsi (induk, anak) {
Anak = anak || fungsi(){} ;
if (Parent === tidak ditentukan)
anak kembali;
// pinjam konstruktor kelas induk
Anak = fungsi () {
Parent.Apply (ini, argumen);
};
// mewarisi prototipe kelas induk melalui salinan yang dalam
Child.Prototype = DeepClone (Parent.Prototype);
// Atribut Konstruktor Reset
Child.prototype.constructor = anak;
};
Meringkaskan
Setelah mengatakan begitu banyak, pada kenyataannya, menerapkan warisan dalam JavaScript sangat fleksibel dan beragam, dan tidak ada metode terbaik. Metode pewarisan yang berbeda perlu diimplementasikan sesuai dengan kebutuhan yang berbeda. Yang paling penting adalah memahami prinsip menerapkan warisan dalam JavaScript, yaitu, masalah prototipe dan rantai prototipe. Selama Anda memahami ini, Anda dapat dengan mudah menerapkan warisan sendiri.