Rumah > hujung hadapan web > tutorial js > Penjelasan terperinci tentang warisan prototaip dalam petua JavaScript_javascript

Penjelasan terperinci tentang warisan prototaip dalam petua JavaScript_javascript

WBOY
Lepaskan: 2016-05-16 16:14:05
asal
1095 orang telah melayarinya

JavaScript ialah bahasa berorientasikan objek. Terdapat pepatah yang sangat klasik dalam JavaScript, semuanya adalah objek. Oleh kerana ia berorientasikan objek, ia mempunyai tiga ciri utama berorientasikan objek: enkapsulasi, pewarisan dan polimorfisme. Apa yang saya bincangkan di sini ialah pewarisan JavaScript, dan saya akan bercakap tentang dua yang lain kemudian.

Warisan JavaScript berbeza daripada warisan C adalah berdasarkan kelas, manakala warisan JavaScript adalah berdasarkan prototaip.

Sekarang datang masalah.

Apakah prototaip? Untuk prototaip, kita boleh merujuk kepada kelas dalam C, yang juga menyimpan sifat dan kaedah objek. Contohnya, mari tulis objek ringkas

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
This.name = nama;
}
Animal.prototype.setName = fungsi(nama) {
This.name = nama;
}
var animal = new Animal("wangwang");

Kita dapat melihat bahawa ini ialah objek Haiwan, yang mempunyai nama atribut dan setName kaedah. Perlu diingat bahawa sebaik sahaja prototaip diubah suai, seperti menambah kaedah, semua contoh objek akan berkongsi kaedah ini. Contohnya

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
This.name = nama;
}
var animal = new Animal("wangwang");

Pada masa ini, haiwan hanya mempunyai atribut nama. Jika kita menambah ayat,

Salin kod Kod adalah seperti berikut:

Animal.prototype.setName = fungsi(nama) {
This.name = nama;
}

Pada masa ini, haiwan juga akan mempunyai kaedah setName.

Warisi salinan ini - bermula dari objek kosong Kita tahu bahawa antara jenis asas JS, terdapat satu objek yang dipanggil, dan contoh paling asasnya ialah objek kosong, iaitu, contoh yang dihasilkan dengan memanggil Objek baru secara langsung. (), atau Ia diisytiharkan menggunakan literal { }. Objek kosong ialah "objek bersih" dengan hanya sifat dan kaedah yang dipratentukan, dan semua objek lain mewarisi daripada objek kosong, jadi semua objek mempunyai sifat dan kaedah yang dipratentukan ini. Prototaip sebenarnya adalah contoh objek. Maksud prototaip ialah: jika pembina mempunyai objek prototaip A, maka contoh yang dibuat oleh pembina mesti disalin daripada A. Memandangkan tika itu disalin daripada objek A, tika itu mesti mewarisi semua sifat A, kaedah dan sifat lain. Jadi, bagaimana replikasi dicapai? Kaedah 1: Penyalinan pembinaan: Setiap kali kejadian dibina, kejadian disalin daripada prototaip Contoh baharu dan prototaip menduduki ruang memori yang sama. Walaupun ini menjadikan obj1 dan obj2 "sepenuhnya konsisten" dengan prototaip mereka, ia juga sangat tidak ekonomik - penggunaan ruang memori akan meningkat dengan cepat. Seperti yang ditunjukkan dalam gambar:


Kaedah 2: Copy-on-write Strategi ini datang daripada teknik yang secara konsisten menipu sistem: copy-on-write. Contoh tipikal penipuan jenis ini ialah perpustakaan pautan dinamik (DDL) dalam sistem pengendalian, yang kawasan memorinya sentiasa disalin semasa menulis. Seperti yang ditunjukkan dalam gambar:


Kami hanya perlu menunjukkan dalam sistem bahawa obj1 dan obj2 adalah sama dengan prototaip mereka, supaya apabila membaca, kami hanya perlu mengikut arahan untuk membaca prototaip. Apabila kita perlu menulis sifat objek (seperti obj2), kita menyalin imej prototaip dan menunjukkan operasi masa hadapan ke imej ini. Seperti yang ditunjukkan dalam gambar:


Kelebihan kaedah ini ialah kami tidak memerlukan banyak overhed memori semasa membuat kejadian dan atribut membaca Kami hanya menggunakan beberapa kod untuk memperuntukkan memori semasa menulis buat kali pertama, yang membawa beberapa kod dan overhed memori. . Tetapi tiada lagi overhead seperti itu, kerana kecekapan mengakses imej adalah sama seperti mengakses prototaip. Namun, bagi sistem yang kerap melakukan operasi tulis, kaedah ini tidak lebih menjimatkan daripada kaedah sebelumnya. Kaedah 3: Baca traversal Kaedah ini menukar butiran replikasi daripada prototaip kepada ahli. Ciri kaedah ini ialah hanya apabila menulis ahli contoh, maklumat ahli disalin ke imej contoh. Apabila menulis atribut objek, contohnya (obj2.value=10), nilai atribut bernama nilai akan dijana dan diletakkan dalam senarai ahli objek obj2. Tengok gambar:

Boleh didapati bahawa obj2 masih merupakan rujukan yang menunjuk kepada prototaip, dan tiada contoh objek yang sama saiz dengan prototaip dibuat semasa operasi. Dengan cara ini, operasi tulis tidak menghasilkan peruntukan memori yang besar, jadi penggunaan memori menjadi menjimatkan. Perbezaannya ialah obj2 (dan semua contoh objek) perlu mengekalkan senarai ahli. Senarai ahli ini mengikut dua peraturan: Ia dijamin untuk diakses terlebih dahulu apabila membaca Jika tiada atribut dinyatakan dalam objek, percubaan dibuat untuk melintasi keseluruhan rantaian prototaip objek sehingga prototaip kosong atau atribut ditemui. Rantaian prototaip akan dibincangkan kemudian. Jelas sekali, antara tiga kaedah tersebut, read traversal mempunyai prestasi terbaik. Oleh itu, warisan prototaip JavaScript dibaca traversal. constructor Orang yang biasa dengan C pasti akan keliru selepas membaca kod objek teratas. Lebih mudah untuk memahami tanpa kata kunci kelas Lagipun, terdapat kata kunci fungsi, yang hanya kata kunci yang berbeza. Tetapi bagaimana dengan pembina? Malah, JavaScript juga mempunyai pembina yang serupa, tetapi ia dipanggil pembina. Apabila menggunakan operator baharu, pembina sebenarnya telah dipanggil dan ini terikat pada objek. Sebagai contoh, kami menggunakan kod berikut

Salin kod Kod adalah seperti berikut:

var animal = Haiwan("wangwang");

haiwan tidak dapat ditentukan. Sesetengah orang akan mengatakan bahawa tiada nilai pulangan sudah tentu tidak ditentukan. Kemudian jika anda menukar definisi objek Haiwan:

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
This.name = nama;
Kembalikan ini;
}

Teka haiwan apakah sekarang?
Pada masa ini, haiwan itu menjadi tetingkap Perbezaannya ialah tetingkap itu dilanjutkan supaya tetingkap itu mempunyai atribut nama. Ini kerana ini lalai kepada tetingkap, yang merupakan pembolehubah peringkat atas, jika ia tidak dinyatakan. Hanya dengan memanggil kata kunci baharu boleh pembina dipanggil dengan betul. Jadi, bagaimana untuk mengelakkan pengguna daripada kehilangan kata kunci baharu? Kita boleh membuat beberapa perubahan kecil:

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
Jika(!(contoh Haiwan ini)) {
          kembalikan Haiwan(nama);
}
This.name = nama;
}

Dengan cara ini anda akan menjadi kalis mudah. Pembina juga digunakan untuk menunjukkan objek mana yang dimiliki oleh contoh itu. Kita boleh menggunakan instanceof untuk menilai, tetapi instanceof mengembalikan benar untuk kedua-dua objek nenek moyang dan objek sebenar semasa pewarisan, jadi ia tidak sesuai. Apabila pembina dipanggil dengan baru, ia menunjuk ke objek semasa secara lalai.

Salin kod Kod adalah seperti berikut:

console.log(Animal.prototype.constructor === Animal); // true

Kita boleh berfikir secara berbeza: prototaip tidak mempunyai nilai sama sekali apabila fungsi dimulakan mungkin logik berikut

//Tetapkan __proto__ sebagai ahli terbina dalam fungsi dan get_prototyoe() ialah kaedahnya

Salin kod Kod adalah seperti berikut:

var __proto__ = null;
function get_prototype() {
Jika(!__proto__) {
​​​​ __proto__ = Objek baharu();
         __proto__.constructor = ini;
}
Kembalikan __proto__;
}

Kelebihan ini ialah ia mengelakkan mencipta contoh objek setiap kali fungsi diisytiharkan, menjimatkan overhed. Pembina boleh diubah suai, yang akan dibincangkan kemudian. Warisan berasaskan prototaip Saya percaya semua orang tahu hampir apa itu warisan, tetapi saya tidak akan menunjukkan had IQ yang lebih rendah.

Terdapat beberapa jenis warisan JS, berikut adalah dua jenis

1 Kaedah 1 Kaedah ini adalah yang paling biasa digunakan dan mempunyai keselamatan yang lebih baik. Mari kita tentukan dua objek

dahulu

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
This.name = nama;
}
fungsi Anjing(umur) {
This.age = umur;
}
var anjing = Anjing baharu(2);

Untuk membina pewarisan adalah sangat mudah, arahkan prototaip objek anak kepada contoh objek induk (perhatikan bahawa ia adalah contoh, bukan objek)

Salin kod Kod adalah seperti berikut:

Dog.prototype = Haiwan baharu("wangwang");

Pada masa ini, anjing akan mempunyai dua sifat, nama dan umur. Dan jika anda menggunakan instanceof operator

pada anjing

Salin kod Kod adalah seperti berikut:

console.log(anjing instanceof Animal); // true
console.log(anjing instanceof Dog); // false

Dengan cara ini, warisan dicapai, tetapi ada masalah kecil

Salin kod Kod adalah seperti berikut:

console.log(Dog.prototype.constructor === Animal); // true
console.log(Dog.prototype.constructor === Dog); // false

Anda dapat melihat bahawa objek yang ditunjukkan oleh pembina telah berubah, yang tidak memenuhi tujuan kami. Kami tidak boleh menilai milik siapa contoh baharu kami. Oleh itu, kita boleh menambah ayat:

Salin kod Kod adalah seperti berikut:

Dog.prototype.constructor = Anjing;

Mari kita lihat semula:

Salin kod Kod adalah seperti berikut:

console.log(anjing instanceof Animal); // false
console.log(anjing instanceof Dog); // true

selesai. Kaedah ini adalah sebahagian daripada penyelenggaraan rantaian prototaip dan akan diterangkan secara terperinci di bawah. 2. Kaedah 2 Kaedah ini mempunyai kebaikan dan keburukan, tetapi keburukan mengatasi kelebihannya. Mari lihat kod dahulu

Salin kod Kod adalah seperti berikut:

function Animal(name) {
This.name = nama;
}
Animal.prototype.setName = fungsi(nama) {
This.name = nama;
}
fungsi Anjing(umur) {
This.age = umur;
}
Dog.prototype = Haiwan.prototaip;

Ini mencapai penyalinan prototaip.

Kelebihan kaedah ini ialah ia tidak perlu membuat instantiate objek (berbanding kaedah 1), yang menjimatkan sumber. Kelemahannya juga jelas Di samping masalah yang sama seperti di atas, iaitu, pembina menunjuk ke objek induk, ia hanya boleh menyalin sifat dan kaedah yang diisytiharkan oleh objek induk menggunakan prototaip. Dalam erti kata lain, dalam kod di atas, atribut nama objek Haiwan tidak boleh disalin, tetapi kaedah setName boleh disalin. Perkara yang paling mematikan ialah sebarang pengubahsuaian kepada prototaip objek kanak-kanak akan menjejaskan prototaip objek induk, iaitu, kejadian yang diisytiharkan oleh kedua-dua objek akan terjejas. Oleh itu, kaedah ini tidak digalakkan.

Rantai Prototaip

Setiap orang yang mempunyai warisan bertulis tahu bahawa warisan boleh diwarisi pada pelbagai peringkat. Dalam JS, ini membentuk rantaian prototaip. Rantai prototaip telah disebut berkali-kali di atas, jadi apakah rantai prototaip itu? Satu contoh hendaklah sekurang-kurangnya mempunyai atribut proto yang menunjuk kepada prototaip, yang merupakan asas sistem objek dalam JavaScript. Walau bagaimanapun, sifat ini tidak kelihatan. Kami memanggilnya sebagai "rantai prototaip dalaman" untuk membezakannya daripada "rantai prototaip pembina" yang terdiri daripada prototaip pembina (yang biasanya kami panggil "rantai prototaip"). Mula-mula mari kita bina hubungan warisan mudah mengikut kod di atas:

Salin kod Kod adalah seperti berikut:

fungsi Haiwan(nama) {
This.name = nama;
}
fungsi Anjing(umur) {
This.age = umur;
}
var animal = new Animal("wangwang");
Anjing.prototaip = haiwan;
var anjing = Anjing baharu(2);

Sebagai peringatan, seperti yang dinyatakan sebelum ini, semua objek mewarisi objek kosong. Oleh itu, kami membina rantai prototaip:


Kita dapat melihat bahawa prototaip objek kanak-kanak menghala ke contoh objek induk, membentuk rantai prototaip pembina. Objek proto dalaman bagi tika anak juga menunjuk kepada contoh objek induk, membentuk rantai prototaip dalaman. Apabila kita perlu mencari atribut tertentu, kod itu serupa dengan

Salin kod Kod adalah seperti berikut:

fungsi getAttrFromObj(attr, obj) {
If(typeof(obj) === "objek") {
        var proto = obj;
​​​​semasa (proto) {
If(proto.hasOwnProperty(attr)) {
                     kembalikan proto[attr];
            }
              proto = proto.__proto__;
}
}
Kembali tidak ditentukan;
}

Wenn wir in diesem Beispiel nach dem Namensattribut in Hund suchen, wird es in der Mitgliederliste in Hund gesucht. Natürlich wird es nicht gefunden, da die Mitgliederliste von Hund jetzt nur das Alter enthält. Anschließend wird die Suche entlang der Prototypenkette fortgesetzt, dh der Instanz, auf die .proto zeigt, dh im Tier wird das Namensattribut gefunden und zurückgegeben. Wenn nach einer Eigenschaft gesucht wird, die nicht existiert, und diese in animal nicht gefunden werden kann, wird die Suche entlang von .proto fortgesetzt und ein leeres Objekt gefunden. Wenn es diese nicht finden kann, wird die Suche in .proto fortgesetzt leeres Objekt. Proto zeigt auf Null und sucht nach Ausgang.

Wartung der Prototypenkette Wir haben eine Frage aufgeworfen, als wir gerade über die Prototypenvererbung gesprochen haben. Bei der Verwendung von Methode 1 zum Erstellen der Vererbung zeigt der Konstruktor der untergeordneten Objektinstanz auf das übergeordnete Objekt. Dies hat den Vorteil, dass wir über das Konstruktorattribut auf die Prototypenkette zugreifen können, aber auch die Nachteile liegen auf der Hand. Ein Objekt, dessen Instanz es generiert, sollte auf sich selbst zeigen, also

Code kopieren Der Code lautet wie folgt:

(new obj()).prototype.constructor === obj;
Wenn wir dann die Prototypeigenschaft überschreiben, zeigt der Konstruktor der vom Unterobjekt generierten Instanz nicht auf sich selbst! Dies widerspricht der ursprünglichen Absicht des Konstrukteurs. Wir haben oben eine Lösung erwähnt:

Code kopieren Der Code lautet wie folgt:
Dog.prototype = neues Tier("wangwang");
Dog.prototype.constructor = Hund;

Sieht so aus, als gäbe es kein Problem. Tatsächlich bringt dies jedoch ein neues Problem mit sich, da wir feststellen werden, dass wir die Prototypenkette nicht zurückverfolgen können, da wir das übergeordnete Objekt nicht finden können und auf die .proto-Eigenschaft der internen Prototypenkette nicht zugegriffen werden kann. Daher bietet SpiderMonkey eine verbesserte Lösung: Hinzufügen eines Attributs namens __proto__ zu jedem erstellten Objekt, das immer auf den vom Konstruktor verwendeten Prototyp verweist. Auf diese Weise wirken sich Änderungen am Konstruktor nicht auf den Wert von __proto__ aus, was die Wartung des Konstruktors erleichtert.

Es gibt jedoch noch zwei weitere Probleme:

__proto__ ist überschreibbar, was bedeutet, dass bei der Verwendung immer noch Risiken bestehen

__proto__ ist eine spezielle Verarbeitung von SpiderMonkey und kann nicht in anderen Engines (wie JScript) verwendet werden.

Wir haben auch eine andere Möglichkeit, nämlich die Konstruktoreigenschaften des Prototyps beizubehalten und die Konstruktoreigenschaften der Instanz innerhalb der Konstruktorfunktion der Unterklasse zu initialisieren.

Der Code lautet wie folgt: Schreiben Sie das Unterobjekt neu

Code kopieren Der Code lautet wie folgt:
Funktion Hund(Alter) {
This.constructor = arguments.callee;
This.age = Alter;
}
Dog.prototype = neues Tier("wangwang");

Auf diese Weise verweisen die Konstruktoren aller untergeordneten Objektinstanzen korrekt auf das Objekt und der Konstruktor des Prototyps zeigt auf das übergeordnete Objekt. Obwohl diese Methode relativ ineffizient ist, da das Konstruktorattribut jedes Mal neu geschrieben werden muss, wenn eine Instanz erstellt wird, besteht kein Zweifel daran, dass diese Methode den vorherigen Widerspruch effektiv lösen kann. ES5 berücksichtigt diese Situation und löst dieses Problem vollständig: Sie können Object.getPrototypeOf() jederzeit verwenden, um den echten Prototyp eines Objekts zu erhalten, ohne auf den Konstruktor zugreifen oder eine externe Prototypenkette pflegen zu müssen. Um Objekteigenschaften wie im vorherigen Abschnitt erwähnt zu finden, können wir sie daher wie folgt umschreiben:

Code kopieren Der Code lautet wie folgt:
Funktion getAttrFromObj(attr, obj) {
If(typeof(obj) === "object") {
mach {
          var proto = Object.getPrototypeOf(dog);
If(proto[attr]) {
                     return proto[attr];
            }
}
​​​​while(proto);
}
Rückgabe undefiniert;
}

Natürlich kann diese Methode nur in Browsern verwendet werden, die ES5 unterstützen. Aus Gründen der Abwärtskompatibilität müssen wir noch die vorherige Methode berücksichtigen. Eine geeignetere Methode besteht darin, diese beiden Methoden zu integrieren und zu kapseln. Ich glaube, dass die Leser darin sehr gut sind, daher werde ich hier nicht angeben.

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan