memperkenalkan
Bab ini adalah bab kedua tentang implementasi berorientasi objek ECMASCRIPT. Dalam bab pertama, kita membahas perbandingan antara Pendahuluan dan Cemascript. Jika Anda belum membaca bab pertama, sebelum melanjutkan dengan bab ini, saya sangat menyarankan Anda membaca bab pertama terlebih dahulu, karena bab ini terlalu panjang (halaman 35).
Teks Bahasa Inggris Asli: http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-Implementation/
Catatan: Karena artikelnya terlalu panjang, kesalahan tidak dapat dihindari dan mereka terus diperbaiki.
Dalam pendahuluan, kami meluas ke ecmascript. Sekarang, ketika kita tahu bahwa itu adalah implementasi OOP, mari kita tentukan secara akurat:
Salinan kode adalah sebagai berikut:
Ecmascript adalah bahasa pemrograman yang berorientasi objek yang mendukung delegasi warisan berdasarkan prototipe.
Ecmascript adalah bahasa yang berorientasi objek yang mendukung pewarisan berbasis prototipe.
Kami akan menganalisis tipe data paling dasar. Hal pertama yang perlu kita pahami adalah bahwa Ecmascript menggunakan nilai -nilai dan objek primitif untuk membedakan entitas. Oleh karena itu, "dalam JavaScript, semuanya adalah objek" yang disebutkan dalam beberapa artikel salah (tidak sepenuhnya benar), dan nilai primitifnya adalah beberapa tipe data yang akan kita diskusikan di sini.
Tipe data
Meskipun ecmascript adalah bahasa tipe lemah yang dinamis yang dapat dikonversi secara dinamis, ia masih memiliki tipe data. Dengan kata lain, suatu objek harus termasuk tipe nyata.
Ada 9 tipe data yang ditentukan dalam spesifikasi standar, tetapi hanya 6 yang dapat diakses secara langsung dalam program ecmascript. Mereka adalah: tidak terdefinisi, nol, boolean, string, angka, dan objek.
Tiga jenis lainnya hanya dapat diakses pada tingkat implementasi (tipe ini tidak dapat digunakan oleh objek ecmascript) dan digunakan untuk spesifikasi untuk menjelaskan beberapa perilaku operasional dan menghemat nilai perantara. 3 jenis ini adalah: referensi, daftar, dan penyelesaian.
Oleh karena itu, referensi digunakan untuk menjelaskan operator seperti Delete, TypeOF, dan ini, dan berisi objek dasar dan nama atribut; Daftar menjelaskan perilaku daftar parameter (ketika ekspresi dan panggilan fungsi baru); Penyelesaian digunakan untuk menjelaskan perilaku perilaku, melanjutkan, mengembalikan dan melempar pernyataan.
Tipe nilai primitif
Melihat kembali tipe data yang digunakan dalam program ecmascript dalam 6, 5 pertama adalah tipe nilai primitif, termasuk tidak terdefinisi, nol, boolean, string, angka, dan objek.
Contoh Jenis Nilai Asli:
Salinan kode adalah sebagai berikut:
var a = tidak terdefinisi;
var b = null;
var c = true;
var d = 'tes';
var e = 10;
Nilai -nilai ini diimplementasikan secara langsung di lapisan bawah, mereka bukan objek, jadi tidak ada prototipe, tidak ada konstruktor.
Paman Catatan: Meskipun nilai -nilai asli ini mirip dengan yang biasanya kita gunakan (boolean, string, angka, objek) tetapi bukan hal yang sama. Oleh karena itu, hasil tipeof (true) dan typeof (boolean) berbeda, karena hasil tipeof (boolean) adalah fungsi, sehingga fungsi boolean, string, dan angka memiliki prototipe (bab Atribut Baca dan Tulis berikut juga akan disebutkan).
Jika Anda ingin mengetahui jenis data mana yang terbaik, yang terbaik adalah menggunakan TypeOF. Ada contoh yang perlu dicatat. Jika Anda menggunakan TypeOf untuk menilai jenis nol, hasilnya adalah objek. Mengapa? Karena jenis nol didefinisikan sebagai nol.
Salinan kode adalah sebagai berikut:
waspada (tipe null); // "objek"
Alasan untuk menampilkan "objek" adalah karena spesifikasi menetapkan ini: pengembalian "objek" untuk nilai string typeof dari nilai nol.
Spesifikasi tidak membayangkan menjelaskan hal ini, tetapi Brendan Eich (penemu JavaScript) memperhatikan bahwa null sebagian besar digunakan untuk objek untuk tampil relatif terhadap tidak terdefinisi, seperti mengatur objek ke referensi nol. Namun, beberapa dokumen memiliki beberapa gangguan untuk mengaitkannya dengan bug, dan menempatkan bug di daftar bug yang Brendan Eich juga berpartisipasi dalam diskusi. Hasilnya adalah bahwa jika Anda melepaskannya, Anda harus mengatur hasil tipe null ke objek (meskipun standar 262-3 mendefinisikan jenis nol sebagai nol, dan 262-5 telah mengubah standar menjadi nol sebagai objek).
Tipe objek
Selanjutnya, tipe objek (jangan bingung dengan konstruktor objek, sekarang hanya tipe abstrak yang dibahas) adalah satu -satunya tipe data yang menjelaskan objek ecmascript.
Objek adalah kumpulan pasangan nilai kunci yang tidak tertib.
Objek adalah koleksi yang tidak tertib yang berisi pasangan nilai kunci
Nilai kunci suatu objek disebut properti, dan properti adalah wadah dari nilai asli dan objek lainnya. Jika nilai properti adalah fungsi, kami menyebutnya metode.
Misalnya:
Salinan kode adalah sebagai berikut:
var x = {// objek "x" memiliki 3 properti: a, b, c
A: 10, // Nilai Asli
B: {Z: 100}, // Objek "B" memiliki atribut z
c: function () {// function (metode)
alert ('Metode x.c');
}
};
peringatan (xa); // 10
peringatan (xb); // [objek objek]
peringatan (xbz); // 100
xc (); // 'Metode x.c'
Dinamis
Seperti yang kami tunjukkan di Bab 17, objek dalam ES benar -benar dinamis. Ini berarti bahwa ketika program dijalankan, kami dapat menambah, memodifikasi atau menghapus properti objek sesuka hati.
Misalnya:
Salinan kode adalah sebagai berikut:
var foo = {x: 10};
// Tambahkan atribut baru
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// Ubah nilai properti ke suatu fungsi
foo.x = function () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Hapus atribut
hapus foo.x;
console.log (foo); // {y: 20}
Beberapa properti tidak dapat dimodifikasi - (properti baca saja, properti yang dihapus, atau sifat yang tidak dapat dikonfigurasi). Kami akan menjelaskannya dalam karakteristik atribut nanti.
Selain itu, spesifikasi ES5 menetapkan bahwa objek statis tidak dapat memperluas atribut baru, dan halaman properti mereka tidak dapat dihapus atau dimodifikasi. Mereka disebut objek beku, yang dapat diperoleh dengan menerapkan objek. Metode Freeze (O).
Salinan kode adalah sebagai berikut:
var foo = {x: 10};
// bekukan objek
Objek.freeze (foo);
console.log (object.isfrozen (foo)); // BENAR
// tidak dapat dimodifikasi
foo.x = 100;
// tidak dapat berkembang
foo.y = 200;
// tidak bisa dihapus
hapus foo.x;
console.log (foo); // {x: 10}
Dalam spesifikasi ES5, metode objek.preventextensions (O) juga digunakan untuk mencegah ekstensi, atau metode objek.Defineproperty (O) digunakan untuk menentukan sifat:
Salinan kode adalah sebagai berikut:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Nilai: 20,
Tulisan: false, // Baca saja
dapat dikonfigurasi: false // tidak dapat dikonfigurasi
});
// tidak dapat dimodifikasi
foo.y = 200;
// tidak bisa dihapus
hapus foo.y; // PALSU
// Ekspansi Pencegahan dan Kontrol
Object.preventextensions (FOO);
console.log (object.isextensible (foo)); // PALSU
// tidak dapat menambahkan atribut baru
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Objek bawaan, objek asli dan objek host
Penting untuk dicatat bahwa spesifikasi juga membedakan objek bawaan ini, objek elemen dan objek host.
Objek bawaan dan objek elemen didefinisikan dan diimplementasikan oleh spesifikasi ecmascript, dan perbedaan antara keduanya sepele. Semua objek implementasi ecmascript adalah objek asli (beberapa di antaranya adalah objek bawaan, beberapa dibuat ketika program dijalankan, seperti objek yang ditentukan pengguna). Objek bawaan adalah subset dari objek asli dan dibangun ke dalam ecmascript sebelum program dimulai (misalnya, parseint, kecocokan, dll.). Semua objek host disediakan oleh lingkungan host, biasanya browser, dan dapat mencakup, misalnya, jendela, peringatan, dll.
Perhatikan bahwa objek host dapat diimplementasikan oleh ES itu sendiri dan sepenuhnya sesuai dengan semantik normatif. Dalam hal ini, mereka dapat disebut objek "host asli" (sesegera mungkin), tetapi norma tidak menentukan konsep objek "host asli".
Objek boolean, string dan angka
Selain itu, spesifikasi juga mendefinisikan beberapa kelas pembungkus khusus asli, objek -objek ini adalah:
1. Objek Boolean
2. Objek String
3. Objek Digital
Objek-objek ini dibuat oleh konstruktor bawaan yang sesuai dan berisi nilai-nilai asli sebagai sifat internalnya. Objek -objek ini dapat dikonversi untuk menyimpan nilai asli dan sebaliknya.
Salinan kode adalah sebagai berikut:
var c = boolean baru (true);
var d = string baru ('tes');
var e = angka baru (10);
// Konversi ke nilai asli
// Gunakan fungsi tanpa kata kunci baru
с = boolean (c);
d = string (d);
e = angka (e);
// Rekonsversi ke objek
с = objek (c);
d = objek (D);
e = objek (e);
Selain itu, ada objek yang dibuat oleh konstruktor built-in khusus: fungsi (konstruktor objek fungsi), array (konstruktor array), regexp (konstruktor ekspresi reguler), matematika (modul matematika), tanggal (tanggal konstruktor), dll. Objek ini juga merupakan nilai jenis objek objek. Perbedaan mereka dikelola oleh sifat internal. Kami akan membahas hal -hal di bawah ini.
Harfiah
Untuk nilai tiga objek: objek, array dan ekspresi reguler, mereka memiliki pengidentifikasi yang disingkat yang disebut inisialisasi objek, inisialisasi array, dan inisialisasi ekspresi reguler:
Salinan kode adalah sebagai berikut:
// setara dengan array baru (1, 2, 3);
// atau array = array baru ();
// array [0] = 1;
// array [1] = 2;
// array [2] = 3;
var array = [1, 2, 3];
// setara dengan
// var objek = objek baru ();
// objek.a = 1;
// objek.b = 2;
// objek.c = 3;
var objek = {a: 1, b: 2, c: 3};
// setara dengan regexp baru ("^// d+$", "g")
var re =/^/d+$/g;
Perhatikan bahwa jika ketiga objek di atas dipindahkan ke tipe baru, semantik implementasi berikutnya digunakan sesuai dengan jenis baru. Misalnya, dalam badak saat ini dan versi lama Spidermonkey 1.7, objek akan berhasil dibuat dengan konstruktor kata kunci baru, tetapi semantik dari beberapa implementasi (laba -laba/pelacur saat ini) literal mungkin tidak berubah setelah jenis diubah.
Salinan kode adalah sebagai berikut:
var getClass = object.prototype.toString;
Objek = angka;
var foo = objek baru;
waspada ([foo, getClass.call (foo)]); // 0, "[nomor objek]"
var bar = {};
// Rhino, Spidermonkey 1.7- 0, "[Nomor Objek]"
// orang lain: masih "[objek objek]", "[objek objek]"
waspada ([bar, getClass.call (bar)]);
// array memiliki efek yang sama
Array = angka;
foo = array baru;
waspada ([foo, getClass.call (foo)]); // 0, "[nomor objek]"
bar = [];
// Rhino, Spidermonkey 1.7- 0, "[Nomor Objek]"
// orang lain: masih "", "[objek objek]"
waspada ([bar, getClass.call (bar)]);
// Tetapi untuk Regexp, semantik literal tidak berubah. semantik literal
// tidak diubah dalam semua implementasi yang diuji
Regexp = angka;
foo = regexp baru;
waspada ([foo, getClass.call (foo)]); // 0, "[nomor objek]"
bar = /(?!) /g;
waspada ([bar, getClass.call (bar)]); ///(?!)/g, "[objek regexp]"
Regex Literals dan Objek Regexp
Perhatikan bahwa dalam dua contoh berikut, dalam spesifikasi edisi ketiga, semantik ekspresi reguler setara. Regexp literal hanya ada dalam satu kalimat dan dibuat pada tahap parsing. Namun, konstruktor RegExp membuat objek baru, jadi ini dapat menyebabkan beberapa masalah, seperti nilai LastIndex salah selama pengujian:
Salinan kode adalah sebagai berikut:
untuk (var k = 0; k <4; k ++) {
var re = /ecma /g;
peringatan (re.LastIndex); // 0, 4, 0, 4
alert (re.test ("ecmascript")); // Benar, Salah, Benar, Salah
}
// Membandingkan
untuk (var k = 0; k <4; k ++) {
var re = regexp baru ("ecma", "g");
peringatan (re.LastIndex); // 0, 0, 0, 0
alert (re.test ("ecmascript")); // Benar, Benar, Benar, Benar
}
Catatan: Namun, masalah ini telah diperbaiki dalam spesifikasi ES di edisi ke -5. Terlepas dari apakah mereka didasarkan pada literal atau konstruktor, mereka membuat objek baru.
Array asosiatif
Berbagai diskusi statis teks, objek JavaScript (sering dibuat dengan objek initializer {}) disebut tabel hash dan tabel hash atau judul sederhana lainnya: hash (konsep dalam ruby atau perl), array manajemen (konsep dalam php), kamus (konsep dalam python), dll.
Hanya ada istilah seperti itu, terutama karena strukturnya serupa, yaitu, menggunakan pasangan "nilai kunci" untuk menyimpan objek, yang sepenuhnya sesuai dengan struktur data yang ditentukan oleh teori "asosiasi array" atau "tabel hash". Selain itu, tipe data abstrak tabel hash biasanya digunakan pada tingkat implementasi.
Namun, meskipun konsep tersebut dijelaskan dalam istilah, ini sebenarnya adalah kesalahan. Dari perspektif ecmascript: ecmascript hanya memiliki satu objek, jenis dan subtipe, yang tidak berbeda dari "nilai kunci" dengan penyimpanan, jadi tidak ada konsep khusus tentang ini. Karena sifat internal dari objek apa pun dapat disimpan sebagai pasangan kunci "pasangan:
Salinan kode adalah sebagai berikut:
var a = {x: 10};
a ['y'] = 20;
AZ = 30;
var b = angka baru (1);
bx = 10;
oleh = 20;
b ['z'] = 30;
var c = fungsi baru ('');
cx = 10;
cy = 20;
c ['z'] = 30;
// dll, subtipe objek "subtipe" apa pun
Selain itu, karena objek dapat kosong dalam ecmascript, konsep "hash" juga salah di sini:
Salinan kode adalah sebagai berikut:
Object.prototype.x = 10;
var a = {}; // Buat "hash" kosong
waspada (a ["x"]); // 10, tapi tidak kosong
waspada (a.tostring); // fungsi
a ["y"] = 20; // Tambahkan pasangan nilai kunci baru ke "hash"
waspada (a ["y"]); // 20
Object.prototype.y = 20; // Tambahkan atribut prototipe
hapus ["y"]; // Menghapus
waspada (a ["y"]); // Tapi di sini kuncinya dan nilainya masih bernilai 20
Harap dicatat bahwa standar ES5 memungkinkan kami untuk membuat objek yang tidak diketahui (diimplementasikan menggunakan metode Object.Create (NULL)). Dari perspektif ini, objek seperti itu dapat disebut tabel hash:
Salinan kode adalah sebagai berikut:
var ahashtable = object.create (null);
console.log (ahashtable.tostring); // Belum diartikan
Selain itu, beberapa properti memiliki metode pengambil/setter spesifik, sehingga juga dapat menyebabkan kebingungan tentang konsep ini:
Salinan kode adalah sebagai berikut:
var a = string baru ("foo");
a ['length'] = 10;
waspada (a ['length']); // 3
Namun, bahkan jika "hash" mungkin memiliki "prototipe" (misalnya, kelas yang mendelegasikan objek hash dalam ruby atau python), istilah ini tidak benar dalam ecmascript karena tidak ada perbedaan semantik antara dua notasi (mis., Notasi titik AB dan notasi ["b"]).
Konsep "atribut properti" dalam ecmascript tidak dipisahkan secara semantik dari "kunci", indeks array, dan metode. Di sini, semua bacaan atribut dan penulisan objek harus mengikuti aturan terpadu: Periksa rantai prototipe.
Dalam contoh Ruby berikut, kita dapat melihat perbedaan semantik:
Salinan kode adalah sebagai berikut:
a = {}
A.Class # hash
A.Length # 0
# pasangan "key-value" baru
a ['length'] = 10;
# Secara semantik, atribut atau metode yang diakses dengan satu titik, bukan kunci
A.Length # 1
# Indexer mengakses kunci di hash
a ['length'] # 10
# Mirip dengan dinyatakan secara dinamis kelas hash pada objek yang ada
# Kemudian nyatakan atribut atau metode baru
hash kelas
def z
100
akhir
akhir
# Properti baru dapat diakses
AZ # 100
# Tapi bukan "kunci"
a ['z'] # nil
Standar ECMA-262-3 tidak menentukan konsep "hash" (dan serupa). Namun, jika ada teori struktural seperti itu, objek yang dinamai sesuai dengan ini.
Konversi objek
Untuk mengonversi objek menjadi nilai primitif, Anda dapat menggunakan metode nilai. Seperti yang kami katakan, ketika konstruktor fungsi disebut sebagai fungsi (untuk beberapa jenis), tetapi jika Anda tidak menggunakan kata kunci baru, Anda mengonversi objek menjadi nilai asli, itu setara dengan nilai metode metode implisit:
Salinan kode adalah sebagai berikut:
var a = angka baru (1);
var primitivea = angka (a); // panggilan "nilai" implisit
var alsoprimitivea = a.valueof (); // Panggilan Eksplisit
peringatan([
typeof a, // "objek"
Jenis primitive, // "nomor"
typeof alsoprimitivea // "nomor"
]);
Metode ini memungkinkan objek untuk berpartisipasi dalam berbagai operasi, seperti:
Salinan kode adalah sebagai berikut:
var a = angka baru (1);
var b = angka baru (2);
waspada (a + b); // 3
// bahkan
var c = {
X: 10,
Y: 20,
valueof: function () {
kembalikan this.x + this.y;
}
};
var d = {
X: 30,
Y: 40,
// fungsi yang sama dengan nilai C
Nilai: c.valueof
};
Peringatan (C + D); // 100
Nilai default nilai akan berubah sesuai dengan jenis objek (jika tidak diganti). Untuk beberapa objek, ia mengembalikan ini - misalnya: objek.prototype.valueof (), dan juga nilai yang dihitung: date.prototype.valueof () Mengembalikan tanggal dan waktu:
Salinan kode adalah sebagai berikut:
var a = {};
waspada (a.valueof () === a); // Benar, "nilai" mengembalikan ini
var d = tanggal baru ();
peringatan (d.valueof ()); // waktu
peringatan (d.valueof () === D.GetTime ()); // BENAR
Selain itu, objek memiliki representasi yang lebih primitif - tampilan string. Metode ToString ini dapat diandalkan dan digunakan secara otomatis dalam beberapa operasi:
Salinan kode adalah sebagai berikut:
var a = {
valueof: function () {
mengembalikan 100;
},
tostring: function () {
kembali '__test';
}
};
// Dalam operasi ini, metode tostring secara otomatis dipanggil
waspada (a); // "__tes"
// Tapi di sini, metode nilai () disebut
Peringatan (A + 10); // 110
// Namun, setelah nilai dihapus
// tostring dapat dipanggil secara otomatis lagi
hapus a.valueof;
Peringatan (A + 10); // "_test10"
Metode ToString yang didefinisikan pada objek.Prototype memiliki signifikansi khusus, dan mengembalikan nilai atribut internal [[kelas]] yang akan kita bahas di bawah ini.
Dibandingkan dengan konversi ke nilai asli (topRimitive), ada juga spesifikasi konversi (TOOBJECT) untuk mengubah nilai menjadi tipe objek.
Metode eksplisit adalah dengan menggunakan konstruktor objek bawaan sebagai fungsi untuk memanggil TOObject (sesuatu seperti menggunakan kata kunci baru OK):
Salinan kode adalah sebagai berikut:
var n = objek (1); // [nomor objek]
var s = objek ('tes'); // [string objek]
// Beberapa kesamaan juga dimungkinkan dengan operator baru
var b = objek baru (true); // [objek boolean]
// Jika Anda menerapkan parameter objek baru, Anda membuat objek sederhana
var o = objek baru (); // [objek objek]
// Jika parameternya adalah objek yang ada
// Hasil penciptaan adalah dengan sekadar mengembalikan objek
var a = [];
Peringatan (A === Objek Baru (A)); // BENAR
waspada (a === objek (a)); // BENAR
Mengenai panggilan konstruktor bawaan, tidak ada aturan umum untuk menggunakan atau tidak menerapkan operator baru, tergantung pada konstruktor. Misalnya, array atau fungsi menggunakan konstruktor operator baru atau fungsi sederhana yang tidak menggunakan operator baru untuk menghasilkan hasil yang sama:
Salinan kode adalah sebagai berikut:
var a = array (1, 2, 3); // [array objek]
var b = array baru (1, 2, 3); // [array objek]
var c = [1, 2, 3]; // [array objek]
var d = fungsi (''); // [fungsi objek]
var e = fungsi baru (''); // [fungsi objek]
Ketika beberapa operator digunakan, ada juga beberapa tampilan dan konversi implisit:
Salinan kode adalah sebagai berikut:
var a = 1;
var b = 2;
// implisit
var c = a + b; // 3, angka
var d = a + b + '5' // "35", string
// eksplisit
var e = '10'; // "10", string
var f = +e; // 10, angka
var g = parseInt (e, 10); // 10, angka
// dll
Sifat atribut
Semua properti dapat memiliki banyak atribut.
1. {ReadOnly} - Abaikan operasi tulis dari penetapan nilai ke atribut, tetapi atribut read -only dapat diubah oleh perilaku lingkungan host - yaitu, mereka bukan "nilai konstan";
2. {dontenum}-Atribut tidak dapat disebutkan oleh untuk..in loop
3. {DontDelete}-Perilaku operator hapus diabaikan (yaitu, tidak dapat dihapus);
4. {Internal} - Atribut internal, tanpa nama (hanya digunakan pada tingkat implementasi), atribut tersebut tidak dapat diakses dalam ecmascript.
Perhatikan bahwa dalam ES5 {readonly}, {dondenum} dan {dontDelete} diganti namanya menjadi [[writable]], [[enumerable]] dan [[dapat dikonfigurasi]], dan properti ini dapat dikelola secara manual melalui objek.defineproperty atau metode serupa.
Salinan kode adalah sebagai berikut:
var foo = {};
Object.defineproperty (foo, "x", {
Nilai: 10,
Writable: true, // yaitu {readonly} = false
enumerable: false, // yaitu {dondenum} = true
Konfigurasi: true // yaitu {dontDelete} = false
});
console.log (foo.x); // 10
// Dapatkan atribut melalui deskripsi
var desc = object.getOwnPropertyDescriptor (foo, "x");
console.log (desc.enumerable); // PALSU
console.log (desc.wranting); // BENAR
// dll
Sifat dan metode internal
Objek juga dapat memiliki properti internal (bagian dari tingkat implementasi), dan program ecmascript tidak dapat mengakses secara langsung (tetapi kita akan melihat di bawah ini bahwa beberapa implementasi memungkinkan akses ke beberapa properti tersebut). Properti ini diakses melalui tanda kurung bersarang [[]]. Mari kita lihat beberapa properti ini. Deskripsi properti ini dapat ditemukan dalam spesifikasi.
Setiap objek harus mengimplementasikan sifat dan metode internal berikut:
1. [[Prototipe]] - Prototipe objek (yang akan diperkenalkan secara rinci di bawah)
2. [[Kelas]] - Representasi objek string (misalnya, array objek, objek fungsi, fungsi, dll.); digunakan untuk membedakan benda
3. [[get]]-Metode untuk mendapatkan nilai atribut
4. [[put]]-Metode pengaturan nilai atribut
5. [[canput]] - Periksa apakah atributnya dapat ditulis
6. [[HASPROPERTY]]-Periksa apakah objek sudah memiliki properti ini
7. [[hapus]] - Hapus properti ini dari objek
8. [[DefaultValue]] Mengembalikan nilai asli objek untuk (panggil metode nilai dari metode, dan beberapa objek dapat melempar pengecualian typeError).
Nilai properti internal [[kelas]] dapat diperoleh secara tidak langsung melalui metode object.prototype.tostring (), yang seharusnya mengembalikan string berikut: "[objek" + [[kelas]] + "]". Misalnya:
Salinan kode adalah sebagai berikut:
var getClass = object.prototype.toString;
getClass.call ({}); // [objek objek]
getClass.call ([]); // [array objek]
getClass.call (nomor baru (1)); // [nomor objek]
// dll
Fungsi ini biasanya digunakan untuk memeriksa objek, tetapi dalam spesifikasi, [[kelas]] dari objek host dapat berupa nilai apa pun, termasuk nilai atribut [[kelas]] dari objek bawaan, sehingga secara teoritis tidak dapat 100% akurat. Misalnya, properti [[kelas]] dari dokumen.childnodes.item (...) mengembalikan "string" di IE, tetapi "fungsi" yang dikembalikan dalam implementasi lain memang.
Salinan kode adalah sebagai berikut:
// di IE - "string", di lainnya - "fungsi"
alert (getClass.call (document.childnodes.item));
Konstruktor
Jadi, seperti yang kami sebutkan di atas, objek dalam ecmascript dibuat melalui apa yang disebut konstruktor.
Konstruktor adalah fungsi yang membuat dan menginisialisasi objek yang baru dibuat.
Konstruktor adalah fungsi yang membuat dan menginisialisasi objek yang baru dibuat.
Pembuatan Objek (Alokasi Memori) adalah tanggung jawab metode internal konstruktor [[konstruk]]. Perilaku metode internal ini didefinisikan, dan semua konstruktor menggunakan metode ini untuk mengalokasikan memori untuk objek baru.
Inisialisasi dikelola dengan memanggil fungsi naik dan turun objek baru, yang bertanggung jawab dengan metode internal konstruktor [[panggilan]].
Perhatikan bahwa kode pengguna hanya dapat diakses pada fase inisialisasi, meskipun kita dapat mengembalikan objek yang berbeda selama fase inisialisasi (mengabaikan objek TIHS yang dibuat pada fase pertama):
Salinan kode adalah sebagai berikut:
fungsi a () {
// Perbarui objek yang baru dibuat
this.x = 10;
// tapi pengembalian adalah objek yang berbeda
kembali [1, 2, 3];
}
var a = new a ();
Console.log (Ax, A); tidak terdefinisi, [1, 2, 3]
Mengacu pada Fungsi Bab 15 - Bagian Algoritma untuk Membuat Fungsi, kita dapat melihat bahwa fungsi adalah objek asli, termasuk properti [[konstruk]] dan [call]]] serta prototipe prototipe yang ditampilkan - prototipe objek di masa depan (CATATAN: Native adalah konvensi untuk objek asli objek asli, yang digunakan dalam pseud di bawah) di bawah).
Salinan kode adalah sebagai berikut:
F = new NativeObject ();
F. [[Class]] = "function"
.... // atribut lainnya
F. [[Call]] = <referensi ke fungsi> // fungsi itu sendiri
F. [[Konstruk]] = internalconstructor // konstruktor internal biasa
.... // atribut lainnya
// prototipe objek yang dibuat oleh f konstruktor
__ObjectPrototype = {};
__ObjectPrototype.constructor = f // {dondenum}
F.prototype = __ObjectPrototype
[[Call]]] adalah cara utama untuk membedakan objek kecuali atribut [[kelas]] (yang setara dengan "fungsi" di sini), sehingga atribut internal [[panggilan]] dari objek disebut sebagai fungsi. Jika objek seperti itu menggunakan operator Typeof, ia mengembalikan "fungsi". Namun, ini terutama terkait dengan objek asli. Dalam beberapa kasus, implementasi menggunakan tipeof untuk mendapatkan nilai secara berbeda, seperti efek window.alert (...) di IE:
Salinan kode adalah sebagai berikut:
// IE browser - "objek", "objek", browser lain - "fungsi", "fungsi"
alert (object.prototype.tostring.call (window.alert));
peringatan (typeof window.alert); // "objek"
Metode internal [[konstruk]] diaktifkan dengan menggunakan konstruktor dengan operator baru, seperti yang kami katakan, bertanggung jawab untuk alokasi memori dan pembuatan objek. Jika tidak ada parameter, tanda kurung untuk memanggil konstruktor juga dapat dihilangkan:
Salinan kode adalah sebagai berikut:
fungsi a (x) {// konstruktor а
this.x = x || 10;
}
// Jika Anda tidak melewati parameter, tanda kurung juga dapat dihilangkan
var a = baru a; // atau a baru ();
peringatan (kapak); // 10
// secara eksplisit lulus parameter x
var b = a a (20) baru;
peringatan (bx); // 20
Kita juga tahu bahwa SHI dalam konstruktor (fase inisialisasi) diatur ke objek yang baru dibuat.
Mari kita pelajari algoritma untuk pembuatan objek.
Algoritma Pembuatan Objek
Perilaku metode internal [[konstruk]] dapat digambarkan sebagai berikut:
Salinan kode adalah sebagai berikut:
F. [[Konstruk]] (InitialParameter):
O = new NativeObject ();
// properti [[kelas]] diatur ke "objek"
O. [[Class]] = "objek"
// Dapatkan objek G saat mengacu pada F.Prototype
var __ObjectPrototype = f.prototype;
// Jika __ObjectPrototype adalah objek, maka:
O. [[Prototipe]] = __ObjectPrototype
// Jika tidak:
O. [[Prototipe]] = Object.Prototype;
// di sini o. [Prototipe]] adalah prototipe objek objek
// f. [[Call]] diterapkan ketika objek yang baru dibuat diinisialisasi
// Atur ini sebagai objek yang baru dibuat o
// Parameternya sama dengan Parameter Awal di F
R = f. [[Call]] (InitialParameters); ini === o;
// di sini r adalah nilai pengembalian [[panggilan]]
// di JS, seperti ini:
// r = f.Apply (o, initialparameters);
// jika r adalah objek
Return r
// Jika tidak
kembali o
Harap dicatat dua fitur utama:
1. Pertama, prototipe objek yang baru dibuat diperoleh dari properti prototipe fungsi saat ini (ini berarti bahwa properti prototipe dari dua objek yang dibuat oleh konstruktor yang sama dapat berbeda karena sifat prototipe fungsi juga dapat berbeda).
2. Kedua, seperti yang kami sebutkan di atas, jika [[panggilan]] mengembalikan objek ketika objek diinisialisasi, ini persis hasil yang digunakan untuk seluruh operator baru:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype.x = 10;
var a = new a ();
peringatan (kapak); // 10 Dapatkan dari prototipe
// Atur properti .prototype ke objek baru
// Mengapa secara eksplisit menyatakan atribut konstruktor akan dijelaskan di bawah ini
A.prototype = {
Konstruktor: A,
Y: 100
};
var b = new a ();
// Objek "B" memiliki properti baru
peringatan (bx); // belum diartikan
waspada (oleh); // 100 dapatkan dari prototipe
// Tapi prototipe objek A masih bisa mendapatkan hasil aslinya
peringatan (kapak); // 10 - Dapatkan dari prototipe
fungsi b () {
this.x = 10;
mengembalikan array baru ();
}
// Jika konstruktor "B" tidak mengembalikan (atau mengembalikan ini)
// Lalu objek ini dapat digunakan, tetapi situasi berikut mengembalikan array
var b = baru b ();
peringatan (bx); // belum diartikan
alert (object.prototype.tostring.call (b)); // [array objek]
Mari kita pelajari lebih lanjut tentang prototipe
prototipe
Setiap objek memiliki prototipe (kecuali beberapa objek sistem). Komunikasi prototipe dilakukan melalui akses internal, implisit, dan tidak langsung ke [[prototipe]] properti prototipe. Prototipe dapat berupa objek atau nilai nol.
Konstruktor Properti
Contoh di atas memiliki dua poin pengetahuan penting. Yang pertama adalah tentang properti prototipe dari properti konstruktor fungsi. Dalam algoritma penciptaan fungsi, kita tahu bahwa properti konstruktor ditetapkan sebagai properti prototipe fungsi selama tahap penciptaan fungsi. Nilai properti konstruktor adalah referensi penting untuk fungsi itu sendiri:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
var a = new a ();
waspada (a.constructor); // berfungsi a () {}, dengan delegasi
waspada (a.constructor === a); // BENAR
Biasanya dalam hal ini, ada kesalahpahaman: Adalah salah untuk membangun properti sebagai objek yang baru dibuat itu sendiri, tetapi, seperti yang dapat kita lihat, properti ini milik prototipe dan mengakses objek melalui warisan.
Dengan mewarisi instance atribut konstruktor, Anda dapat secara tidak langsung mendapatkan referensi ke objek prototipe:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype.x = nomor baru (10);
var a = new a ();
peringatan (a.constructor.prototype); // [objek objek]
peringatan (kapak); // 10, dengan prototipe
// efek yang sama dengan a. [Prototipe]].
waspada (a.constructor.prototype.x); // 10
waspada (a.constructor.prototype.x === ax); // BENAR
Tetapi harap dicatat bahwa sifat konstruktor dan prototipe fungsi dapat didefinisikan ulang setelah objek dibuat. Dalam hal ini, objek kehilangan mekanisme yang disebutkan di atas. Jika Anda mengedit prototipe prototipe suatu elemen melalui atribut prototipe fungsi (tambahkan objek baru atau memodifikasi objek yang ada), Anda akan melihat atribut yang baru ditambahkan pada instance.
Namun, jika kita sepenuhnya mengubah properti prototipe fungsi (dengan menetapkan objek baru), referensi ke konstruktor asli hilang, karena objek yang kita buat tidak termasuk properti konstruktor:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype = {
X: 10
};
var a = new a ();
peringatan (kapak); // 10
waspada (a.constructor === a); // PALSU!
Oleh karena itu, referensi prototipe ke fungsi perlu dipulihkan secara manual:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype = {
Konstruktor: A,
X: 10
};
var a = new a ();
peringatan (kapak); // 10
waspada (a.constructor === a); // BENAR
Perhatikan bahwa meskipun atribut konstruktor dipulihkan secara manual, dibandingkan dengan prototipe yang hilang asli, fitur {Dontenum} hilang, yaitu, pernyataan loop dalam A.Prototype tidak lagi didukung, tetapi dalam spesifikasi edisi ke -5, [enumerable] fitur yang dapat dikendalikan untuk mengendalikan keadaan yang dapat disinari.
Salinan kode adalah sebagai berikut:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Nilai: 20,
enumerable: false // alias {dondenum} = true
});
console.log (foo.x, foo.y); // 10, 20
untuk (var k in foo) {
console.log (k); // Hanya "x"
}
var xdesc = object.getOwnPropertyDescriptor (foo, "x");
var ydesc = object.getOwnPropertyDescriptor (foo, "y");
console.log (
xdesc.enumerable, // true
ydesc.enumerable // false
);
Prototipe eksplisit dan properti implisit [[prototipe]]
Secara umum, tidak benar untuk secara eksplisit merujuk pada prototipe suatu objek melalui properti prototipe fungsi. Ini mengacu pada objek yang sama, properti [prototipe]] dari objek:
a. [[prototipe]] ---> prototipe <---- a.pototype
Selain itu, nilai [prototipe]] dari instance memang diperoleh pada properti prototipe konstruktor.
Namun, mengirimkan atribut prototipe tidak akan mempengaruhi prototipe yang telah dibuat (hanya akan mempengaruhi ketika atribut prototipe dari perubahan konstruktor), yaitu, objek yang baru dibuat memiliki prototipe baru, dan objek yang dibuat masih akan merujuk pada prototipe lama asli (prototipe ini tidak lagi dapat dimodifikasi).
Salinan kode adalah sebagai berikut:
// Situasi sebelum memodifikasi prototipe A.Prototipe
a. [[prototipe]] ---> prototipe <---- a.pototype
// Setelah modifikasi
A.Prototype ---> prototipe baru // Objek baru akan memiliki prototipe ini
a. [[prototipe]] ---> prototipe // pada prototipe asli boot
Misalnya:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype.x = 10;
var a = new a ();
peringatan (kapak); // 10
A.prototype = {
Konstruktor: A,
X: 20
Y: 30
};
// Objek A adalah nilai yang diperoleh dari prototipe minyak mentah melalui referensi [[prototipe]] implisit [
peringatan (kapak); // 10
Peringatan (ay) // tidak ditentukan
var b = new a ();
// tetapi objek baru adalah nilai yang diperoleh dari prototipe baru
peringatan (bx); // 20
Peringatan (oleh) // 30
Oleh karena itu, adalah salah bagi beberapa artikel untuk mengatakan bahwa "modifikasi dinamis prototipe akan memengaruhi semua objek yang memiliki prototipe baru", dan prototipe baru hanya berlaku pada objek yang baru dibuat setelah prototipe dimodifikasi.
Aturan utama di sini adalah: prototipe objek dibuat ketika objek dibuat, dan tidak dapat dimodifikasi menjadi objek baru setelah itu. Jika objek yang sama masih dirujuk, itu dapat dirujuk melalui prototipe eksplisit konstruktor. Setelah objek dibuat, sifat -sifat prototipe hanya dapat ditambahkan atau dimodifikasi.
Atribut __Proto__ non-standar
Namun, beberapa implementasi (seperti spidermonkey) memberikan properti eksplisit __proto__ unstandard untuk merujuk prototipe objek:
Salinan kode adalah sebagai berikut:
Fungsi a () {}
A.prototype.x = 10;
var a = new a ();
peringatan (kapak); // 10
var __newPrototype = {
Konstruktor: A,
X: 20,
Y: 30
};
// referensi ke objek baru
A.prototype = __newPrototype;
var b = new a ();
peringatan (bx); // 20
waspada (oleh); // 30
// Objek "A" masih menggunakan prototipe lama
peringatan (kapak); // 10
waspada (ay); // belum diartikan
// secara eksplisit memodifikasi prototipe
a .__ proto__ = __newPrototype;
// Sekarang objek "A" mengacu pada objek baru
peringatan (kapak); // 20
waspada (ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
Salinan kode adalah sebagai berikut:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // BENAR
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
Salinan kode adalah sebagai berikut:
function A() {}
A.prototype.x = 10;
var a = new a ();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
Salinan kode adalah sebagai berikut:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
Salinan kode adalah sebagai berikut:
function A() {}
A.prototype.x = 10;
var a = new a ();
alert(ax); // 10
alert(a instanceof A); // BENAR
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
Salinan kode adalah sebagai berikut:
function B() {}
var b = baru b ();
alert(b instanceof B); // BENAR
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // BENAR
alert(b instanceof B); // PALSU
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
Salinan kode adalah sebagai berikut:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// inisialisasi konteksnya
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
kembali {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // BENAR
alert(a.method2 === b.method2); // BENAR
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
Salinan kode adalah sebagai berikut:
// Menulis
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
Salinan kode adalah sebagai berikut:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
Salinan kode adalah sebagai berikut:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
Salinan kode adalah sebagai berikut:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
Salinan kode adalah sebagai berikut:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
kembali;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
kembali;
Misalnya:
Salinan kode adalah sebagai berikut:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Meletakkan]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* Tidak ada apa-apa */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
Salinan kode adalah sebagai berikut:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Misalnya:
Salinan kode adalah sebagai berikut:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // belum diartikan
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
Salinan kode adalah sebagai berikut:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
Salinan kode adalah sebagai berikut:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
Salinan kode adalah sebagai berikut:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // belum diartikan
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
mewarisi
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
Salinan kode adalah sebagai berikut:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
Salinan kode adalah sebagai berikut:
1.toString(); // 语法错误!
(1).toString(); // OKE
1..toString(); // OKE
1['toString'](); // OKE
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
Salinan kode adalah sebagai berikut:
fungsi a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new a ();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = baru a ();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = baru b ();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
Salinan kode adalah sebagai berikut:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = baru a (); // Error
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
Salinan kode adalah sebagai berikut:
fungsi a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new a ();
alert([ax, ay]); // 10 (自身), 20 (集成)
fungsi b () {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Mengutip
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = baru b ();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
Salinan kode adalah sebagai berikut:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
anak kembali;
}
因此,继承:
Salinan kode adalah sebagai berikut:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = baru b ();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
Salinan kode adalah sebagai berikut:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
anak kembali;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
Salinan kode adalah sebagai berikut:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = baru b ();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
Salinan kode adalah sebagai berikut:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
anak kembali;
}
// Penggunaan
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
sebagai kesimpulan
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。