Saya tahu bahwa jawaban ini terlambat 3 tahun tetapi saya benar-benar berpikir jawaban saat ini tidak memberikan informasi yang cukup tentang bagaimana pewarisan prototypal lebih baik daripada warisan klasik .
Pertama mari kita lihat argumen paling umum yang menyatakan programmer JavaScript dalam membela pewarisan prototypal (Saya mengambil argumen ini dari kumpulan jawaban saat ini):
- Itu mudah.
- Sangat kuat.
- Ini mengarah ke kode yang lebih kecil, kurang redundan.
- Ini dinamis dan karenanya lebih baik untuk bahasa dinamis.
Sekarang argumen ini semuanya valid, tetapi tidak ada yang peduli menjelaskan mengapa. Ini seperti memberi tahu seorang anak bahwa belajar Matematika itu penting. Tentu saja, tetapi anak itu tentu saja tidak peduli; dan Anda tidak dapat membuat anak seperti Matematika dengan mengatakan bahwa itu penting.
Saya pikir masalah dengan pewarisan prototypal adalah bahwa hal itu dijelaskan dari perspektif JavaScript. Saya suka JavaScript, tetapi warisan prototipal dalam JavaScript salah. Tidak seperti pewarisan klasik ada dua pola pewarisan prototypal:
- Pola prototipe pewarisan prototypal.
- Pola konstruktor pewarisan prototypal.
Sayangnya JavaScript menggunakan pola konstruktor dari warisan prototypal. Ini karena ketika JavaScript dibuat, Brendan Eich (pembuat JS) ingin agar terlihat seperti Java (yang memiliki warisan klasik):
Dan kami mendorongnya sebagai saudara kecil ke Jawa, karena bahasa pelengkap seperti Visual Basic adalah untuk C ++ dalam keluarga bahasa Microsoft pada saat itu.
Ini buruk karena ketika orang menggunakan konstruktor dalam JavaScript mereka memikirkan konstruktor yang diwarisi dari konstruktor lain. Ini salah. Dalam purwarupa objek prototypal mewarisi dari objek lain. Konstruktor tidak pernah masuk ke dalam gambar. Inilah yang membingungkan kebanyakan orang.
Orang-orang dari bahasa seperti Jawa, yang memiliki warisan klasik, menjadi semakin bingung karena walaupun konstruktor terlihat seperti kelas, mereka tidak berperilaku seperti kelas. Seperti yang dinyatakan Douglas Crockford :
Tipuan ini dimaksudkan untuk membuat bahasa tampak lebih akrab bagi programmer yang terlatih secara klasik, tetapi gagal melakukannya, seperti yang dapat kita lihat dari pendapat yang sangat rendah yang dimiliki programmer Java tentang JavaScript. Pola konstruktor JavaScript tidak menarik bagi orang banyak klasik. Itu juga mengaburkan sifat prototipal sejati JavaScript. Akibatnya, ada sangat sedikit programmer yang tahu cara menggunakan bahasa secara efektif.
Itu dia. Langsung dari mulut kuda.
Warisan Prototypal Sejati
Warisan prototipe adalah tentang benda. Objek mewarisi properti dari objek lain. Hanya itu yang ada untuk itu. Ada dua cara membuat objek menggunakan warisan prototypal:
- Buat objek baru.
- Klon objek yang sudah ada dan perluas.
Catatan: JavaScript menawarkan dua cara untuk mengkloning objek - delegasi dan penggabungan . Selanjutnya saya akan menggunakan kata "clone" untuk secara eksklusif merujuk pada pewarisan melalui delegasi, dan kata "copy" untuk secara eksklusif merujuk pada pewarisan melalui penggabungan.
Cukup bicara. Mari kita lihat beberapa contoh. Katakanlah saya memiliki lingkaran jari-jari 5
:
var circle = {
radius: 5
};
Kita dapat menghitung luas dan keliling lingkaran dari jari-jarinya:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Sekarang saya ingin membuat lingkaran jari-jari lain 10
. Salah satu cara untuk melakukan ini adalah:
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
Namun JavaScript menyediakan cara yang lebih baik - delegasi . The Object.create
Fungsi ini digunakan untuk melakukan hal ini:
var circle2 = Object.create(circle);
circle2.radius = 10;
Itu saja. Anda baru saja melakukan pewarisan prototipal dalam JavaScript. Bukankah itu sederhana? Anda mengambil objek, mengkloningnya, mengubah apa pun yang Anda butuhkan, dan hei presto - Anda mendapatkan sendiri objek baru.
Sekarang Anda mungkin bertanya, "Bagaimana ini sederhana? Setiap kali saya ingin membuat lingkaran baru saya harus mengkloning circle
dan secara manual menetapkan radius". Nah solusinya adalah menggunakan fungsi untuk melakukan angkat berat untuk Anda:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
Bahkan Anda bisa menggabungkan semua ini menjadi satu objek literal sebagai berikut:
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
Warisan Prototypal dalam JavaScript
Jika Anda perhatikan dalam program di atas, create
fungsi ini membuat klon circle
, menetapkan yang baru radius
untuk itu dan kemudian mengembalikannya. Inilah yang dilakukan konstruktor dalam JavaScript:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
Pola konstruktor dalam JavaScript adalah pola prototipe terbalik. Alih-alih membuat objek Anda membuat konstruktor. Kata new
kunci mengikat this
pointer di dalam konstruktor ke klon dari prototype
konstruktor.
Kedengarannya membingungkan? Itu karena pola konstruktor dalam JavaScript tidak perlu memperumit banyak hal. Inilah yang sulit dipahami oleh kebanyakan programmer.
Alih-alih memikirkan objek yang diwarisi dari objek lain, mereka memikirkan konstruktor yang diwarisi dari konstruktor lain dan kemudian menjadi sangat bingung.
Ada banyak alasan lain mengapa pola konstruktor dalam JavaScript harus dihindari. Anda dapat membacanya di posting blog saya di sini: Konstruktor vs Prototipe
Jadi apa manfaat warisan prototipal dari warisan klasik? Mari kita kembali ke argumen yang paling umum, dan menjelaskan alasannya .
1. Warisan prototipe sederhana
CMS menyatakan dalam jawabannya:
Menurut pendapat saya, manfaat utama pewarisan prototypal adalah kesederhanaannya.
Mari kita pertimbangkan apa yang baru saja kita lakukan. Kami menciptakan objek circle
yang memiliki radius 5
. Lalu kami mengkloningnya dan memberi klon jari-jari 10
.
Karenanya kita hanya perlu dua hal untuk membuat warisan warisan prototipe berfungsi:
- Cara untuk membuat objek baru (misalnya objek literal).
- Cara untuk memperluas objek yang ada (mis
Object.create
.).
Sebaliknya warisan klasik jauh lebih rumit. Dalam warisan klasik, Anda memiliki:
- Kelas.
- Obyek.
- Antarmuka.
- Kelas Abstrak.
- Kelas Akhir.
- Kelas Dasar Virtual.
- Konstruktor.
- Destructors.
Anda mendapatkan idenya. Intinya adalah bahwa pewarisan prototypal lebih mudah dipahami, lebih mudah diterapkan, dan lebih mudah dipikirkan.
Seperti yang dikatakan Steve Yegge di posting blog klasiknya " Portrait of a N00b ":
Metadata adalah segala jenis deskripsi atau model dari sesuatu yang lain. Komentar dalam kode Anda hanyalah deskripsi bahasa alami dari perhitungan. Apa yang membuat metadata meta-data adalah bahwa itu tidak sepenuhnya diperlukan. Jika saya memiliki anjing dengan beberapa dokumen silsilah, dan saya kehilangan dokumennya, saya masih memiliki anjing yang valid.
Dalam arti yang sama, kelas hanyalah meta-data. Kelas tidak sepenuhnya diwajibkan untuk warisan. Namun beberapa orang (biasanya n00b) merasa kelas lebih nyaman untuk dikerjakan. Ini memberi mereka rasa aman yang salah.
Kita juga tahu bahwa tipe statis hanyalah metadata. Mereka adalah jenis komentar khusus yang ditargetkan untuk dua jenis pembaca: programmer dan kompiler. Tipe statis menceritakan kisah tentang perhitungan, mungkin untuk membantu kedua kelompok pembaca memahami maksud dari program. Tetapi tipe statis dapat dibuang saat runtime, karena pada akhirnya mereka hanya komentar bergaya. Mereka seperti dokumen silsilah: mungkin membuat tipe kepribadian tertentu yang tidak aman lebih bahagia tentang anjing mereka, tetapi anjing itu tentu saja tidak peduli.
Seperti yang saya katakan sebelumnya, kelas memberi orang rasa aman yang salah. Misalnya Anda mendapatkan terlalu banyak NullPointerException
di Jawa bahkan ketika kode Anda terbaca dengan sempurna. Saya menemukan warisan klasik biasanya menghalangi pemrograman, tapi mungkin itu hanya Java. Python memiliki sistem warisan klasik yang menakjubkan.
2. Warisan Prototypal Kuat
Sebagian besar programmer yang berasal dari latar belakang klasik berpendapat bahwa warisan klasik lebih kuat daripada warisan prototypal karena memiliki:
- Variabel pribadi.
- Warisan berganda.
Klaim ini salah. Kita sudah tahu bahwa JavaScript mendukung variabel pribadi melalui penutupan , tetapi bagaimana dengan multiple inheritance? Objek dalam JavaScript hanya memiliki satu prototipe.
Yang benar adalah bahwa warisan mendukung prototip mewarisi dari beberapa prototipe. Warisan prototipe hanya berarti satu objek mewarisi dari objek lain. Sebenarnya ada dua cara untuk mengimplementasikan warisan prototypal :
- Delegasi atau Warisan Diferensial
- Kloning atau Warisan Konsatenatif
Ya JavaScript hanya mengizinkan objek untuk didelegasikan ke satu objek lainnya. Namun itu memungkinkan Anda untuk menyalin properti dari sejumlah objek yang berubah-ubah. Misalnya _.extend
tidak hanya ini.
Tentu saja banyak programmer tidak menganggap ini sebagai warisan yang benar karena instanceof
dan isPrototypeOf
mengatakan sebaliknya. Namun ini dapat dengan mudah diperbaiki dengan menyimpan berbagai prototipe pada setiap objek yang mewarisi dari prototipe melalui gabungan:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Karenanya, warisan purwarupa sama kuatnya dengan warisan klasik. Sebenarnya itu jauh lebih kuat daripada warisan klasik karena dalam warisan prototypal Anda dapat memilih properti mana yang akan disalin dan properti mana yang akan dihilangkan dari prototipe yang berbeda.
Dalam warisan klasik tidak mungkin (atau setidaknya sangat sulit) untuk memilih properti mana yang ingin Anda warisi. Mereka menggunakan kelas dasar virtual dan antarmuka untuk memecahkan masalah berlian .
Namun dalam JavaScript, Anda kemungkinan besar tidak akan pernah mendengar masalah berlian karena Anda dapat mengontrol properti mana yang ingin Anda warisi dan dari prototipe mana.
3. Warisan Prototypal Kurang Redundan
Poin ini sedikit lebih sulit untuk dijelaskan karena warisan klasik tidak selalu mengarah pada kode yang lebih berlebihan. Sebenarnya pewarisan, baik klasik atau prototypal, digunakan untuk mengurangi redundansi dalam kode.
Salah satu argumen bisa jadi sebagian besar bahasa pemrograman dengan warisan klasik diketik secara statis dan mengharuskan pengguna untuk secara eksplisit mendeklarasikan jenis (tidak seperti Haskell yang memiliki pengetikan statis implisit). Karenanya ini mengarah pada kode yang lebih verbose.
Java terkenal karena perilaku ini. Saya ingat dengan jelas Bob Nystrom menyebutkan anekdot berikut dalam posting blognya tentang Pratt Parsers :
Anda harus menyukai tingkat birokrasi Java "tandatangani dalam empat kali lipat" di sini.
Sekali lagi, saya pikir itu hanya karena Java sangat menyebalkan.
Salah satu argumen yang valid adalah bahwa tidak semua bahasa yang memiliki warisan klasik mendukung pewarisan berganda. Sekali lagi Jawa muncul di benak. Ya Java memiliki antarmuka, tetapi itu tidak cukup. Terkadang Anda benar-benar membutuhkan pewarisan berganda.
Karena pewarisan prototypal memungkinkan pewarisan berganda, kode yang membutuhkan pewarisan berganda tidak terlalu berlebihan jika ditulis menggunakan pewarisan prototypal daripada dalam bahasa yang memiliki warisan klasik tetapi tidak memiliki pewarisan berganda.
4. Warisan Prototypal Dinamis
Salah satu keuntungan terpenting dari pewarisan prototypal adalah Anda dapat menambahkan properti baru ke prototipe setelah dibuat. Ini memungkinkan Anda untuk menambahkan metode baru ke prototipe yang akan secara otomatis tersedia untuk semua objek yang didelegasikan ke prototipe itu.
Ini tidak mungkin dalam warisan klasik karena sekali kelas dibuat Anda tidak dapat memodifikasinya saat runtime. Ini mungkin satu-satunya keuntungan terbesar dari warisan purwarupa dibandingkan warisan klasik, dan seharusnya berada di puncak. Namun saya suka menyimpan yang terbaik untuk akhir.
Kesimpulan
Masalah pewarisan prototipe. Sangat penting untuk mendidik programmer JavaScript tentang mengapa meninggalkan pola konstruktor pewarisan prototypal demi pola prototipe pewarisan prototypal.
Kita perlu mulai mengajar JavaScript dengan benar dan itu berarti menunjukkan kepada programmer baru cara menulis kode menggunakan pola prototypal alih-alih pola konstruktor.
Tidak hanya akan lebih mudah untuk menjelaskan warisan prototypal menggunakan pola prototypal, tetapi juga akan membuat programmer lebih baik.
Jika Anda menyukai jawaban ini maka Anda juga harus membaca posting blog saya di " Mengapa Prototypal Inheritance Matters ". Percayalah, Anda tidak akan kecewa.