Javascript kapan harus menggunakan prototipe

93

Saya ingin memahami kapan saat yang tepat untuk menggunakan metode prototipe di js. Haruskah mereka selalu digunakan? Atau adakah kasus di mana menggunakannya tidak disukai dan / atau menimbulkan hukuman kinerja?

Dalam mencari di sekitar situs ini tentang metode umum untuk namespacing di js, tampaknya sebagian besar menggunakan implementasi berbasis non-prototipe: cukup menggunakan objek atau objek fungsi untuk merangkum namespace.

Berasal dari bahasa berbasis kelas, sulit untuk tidak mencoba dan menggambar paralel dan berpikir bahwa prototipe itu seperti "kelas" dan implementasi namespace yang saya sebutkan seperti metode statis.

opl
sumber

Jawaban:

133

Prototipe adalah pengoptimalan .

Contoh bagus untuk menggunakannya dengan baik adalah pustaka jQuery. Setiap kali Anda mendapatkan objek jQuery dengan menggunakan $('.someClass'), objek itu memiliki lusinan "metode". Perpustakaan bisa mencapainya dengan mengembalikan objek:

return {
   show: function() { ... },
   hide: function() { ... },
   css: function() { ... },
   animate: function() { ... },
   // etc...
};

Tetapi itu berarti bahwa setiap objek jQuery dalam memori akan memiliki lusinan slot bernama yang berisi metode yang sama, berulang kali.

Sebaliknya, metode tersebut ditentukan pada prototipe dan semua objek jQuery "mewarisi" prototipe itu untuk mendapatkan semua metode tersebut dengan biaya runtime yang sangat sedikit.

Salah satu bagian yang sangat penting tentang bagaimana jQuery melakukannya dengan benar adalah bahwa ini disembunyikan dari programmer. Ini diperlakukan murni sebagai pengoptimalan, bukan sebagai sesuatu yang harus Anda khawatirkan saat menggunakan pustaka.

Masalah dengan JavaScript adalah bahwa fungsi konstruktor polos mengharuskan pemanggil untuk mengingat untuk mengawalnya newatau jika tidak, fungsi tersebut biasanya tidak berfungsi. Tidak ada alasan bagus untuk ini. jQuery melakukannya dengan benar dengan menyembunyikan omong kosong itu di balik fungsi biasa $, jadi Anda tidak perlu peduli bagaimana objek diimplementasikan.

Agar Anda dapat dengan mudah membuat objek dengan prototipe tertentu, ECMAScript 5 menyertakan fungsi standar Object.create. Versi yang sangat disederhanakan akan terlihat seperti ini:

Object.create = function(prototype) {
    var Type = function () {};
    Type.prototype = prototype;
    return new Type();
};

Itu hanya menangani rasa sakit menulis fungsi konstruktor dan kemudian memanggilnya dengan new.

Kapan Anda menghindari prototipe?

Perbandingan yang berguna adalah dengan bahasa OO populer seperti Java dan C #. Ini mendukung dua jenis warisan:

  • antarmuka warisan, di mana Anda implementsebuah interfacesehingga kelas menyediakan implementasi sendiri yang unik untuk setiap anggota antarmuka.
  • penerapan warisan, di mana Anda extendseorang classyang menyediakan implementasi default dari beberapa metode.

Dalam JavaScript, pewarisan prototipe adalah jenis pewarisan implementasi . Jadi dalam situasi di mana (di C # atau Java) Anda akan diturunkan dari kelas dasar untuk mendapatkan perilaku default, yang kemudian Anda modifikasi kecil melalui penggantian, lalu di JavaScript, pewarisan prototipe masuk akal.

Namun, jika Anda berada dalam situasi di mana Anda akan menggunakan antarmuka di C # atau Java, maka Anda tidak memerlukan fitur bahasa tertentu di JavaScript. Tidak perlu secara eksplisit mendeklarasikan sesuatu yang mewakili antarmuka, dan tidak perlu menandai objek sebagai "mengimplementasikan" antarmuka itu:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it's a duck!

Dengan kata lain, jika setiap "jenis" objek memiliki definisi sendiri dari "metode", maka tidak ada nilai dalam mewarisi dari prototipe. Setelah itu, itu tergantung pada berapa banyak contoh yang Anda alokasikan untuk setiap jenis. Tetapi dalam banyak desain modular, hanya ada satu contoh dari tipe tertentu.

Dan nyatanya, telah dikemukakan oleh banyak orang bahwa pelaksanaan warisan itu jahat . Artinya, jika ada beberapa operasi umum untuk suatu tipe, maka mungkin lebih jelas jika mereka tidak dimasukkan ke dalam kelas dasar / super, tetapi hanya diekspos sebagai fungsi biasa di beberapa modul, yang Anda berikan objeknya Anda ingin mereka mengoperasi.

Daniel Earwicker
sumber
1
Penjelasan yang bagus. Lalu apakah Anda setuju bahwa, karena Anda menganggap prototipe sebagai pengoptimalan, maka prototipe selalu dapat digunakan untuk meningkatkan kode Anda? Saya bertanya-tanya apakah ada kasus di mana menggunakan prototipe tidak masuk akal, atau benar-benar menimbulkan hukuman kinerja.
opl
Dalam tindak lanjut Anda, Anda menyebutkan bahwa "itu tergantung pada berapa banyak contoh yang Anda alokasikan untuk setiap jenis". Tetapi contoh yang Anda rujuk tidak menggunakan prototipe. Di mana gagasan mengalokasikan sebuah instance (apakah Anda masih akan menggunakan "baru" di sini)? Juga: katakanlah metode quack memiliki parameter - apakah setiap pemanggilan duck.quack (param) akan menyebabkan objek baru dibuat dalam memori (mungkin tidak relevan jika memiliki parameter atau tidak)?
opl
3
1. Maksud saya jika ada sejumlah besar contoh dari satu jenis bebek, maka masuk akal untuk memodifikasi contoh sehingga quackfungsinya ada dalam prototipe, di mana banyak contoh bebek dihubungkan. 2. Sintaks literal objek { ... }membuat sebuah instance (tidak perlu menggunakannya new). 3. Memanggil fungsi apa pun JS menyebabkan setidaknya satu objek dibuat dalam memori - ini disebut argumentsobjek dan menyimpan argumen yang diteruskan dalam panggilan: developer.mozilla.org/en/JavaScript/Reference/…
Daniel Earwicker
Terima kasih saya menerima jawaban Anda. Tetapi saya masih memiliki sedikit kebingungan dengan poin Anda (1): Saya tidak memahami apa yang Anda maksud dengan "sejumlah besar contoh satu jenis bebek". Seperti yang Anda katakan di (3) setiap kali Anda memanggil fungsi JS, satu objek dibuat dalam memori - jadi meskipun Anda hanya memiliki satu jenis bebek, bukankah Anda akan mengalokasikan memori setiap kali Anda memanggil fungsi duck (dalam kasus mana yang selalu masuk akal untuk menggunakan prototipe)?
opl
11
+1 Perbandingan dengan jQuery adalah penjelasan pertama yang jelas & ringkas tentang kapan & mengapa menggunakan prototipe yang telah saya baca. Terima kasih banyak.
GFoley83
46

Anda harus menggunakan prototipe jika Anda ingin mendeklarasikan metode "non-statis" dari objek tersebut.

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.
KeatsKelleher
sumber
@keatsKelleher tetapi kita dapat membuat metode non-statis untuk objek dengan hanya mendefinisikan metode di dalam fungsi konstruktor menggunakan thiscontoh this.getA = function(){alert("A")}kan?
Amr Labib
17

Salah satu alasan untuk menggunakan prototypeobjek bawaan adalah jika Anda akan menduplikasi objek beberapa kali yang akan berbagi fungsionalitas yang sama. Dengan melampirkan metode ke prototipe, Anda dapat menghemat metode duplikat yang dibuat untuk setiap newinstance. Tetapi ketika Anda melampirkan metode ke prototype, semua instance akan memiliki akses ke metode tersebut.

Katakanlah Anda memiliki Car()kelas / objek dasar.

function Car() {
    // do some car stuff
}

lalu Anda membuat beberapa Car()contoh.

var volvo = new Car(),
    saab = new Car();

Sekarang, Anda tahu bahwa setiap mobil perlu dikendarai, dihidupkan, dll. Alih-alih melampirkan metode langsung ke Car()kelas (yang membutuhkan memori per setiap instance yang dibuat), Anda dapat melampirkan metode ke prototipe sebagai gantinya (hanya membuat metode sekali), oleh karena itu memberikan akses ke metode tersebut untuk yang baru volvodan saab.

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'
hellatan
sumber
2
sebenarnya ini tidak benar. volvo.honk () tidak akan berfungsi karena Anda mengganti objek prototipe sepenuhnya, tidak memperpanjangnya. Jika Anda melakukan sesuatu seperti ini, ini akan bekerja seperti yang Anda harapkan: Car.prototype.honk = function () {console.log ('HONK');} volvo.honk (); // '
HONK
1
@ 29er - cara saya menulis contoh ini, Anda benar. Urutan itu penting. Jika saya membiarkan contoh ini sebagaimana adanya, Car.prototype = { ... }itu harus datang sebelum memanggil a new Car()seperti yang diilustrasikan dalam jsfiddle ini: jsfiddle.net/mxacA . Adapun argumen Anda, ini adalah cara yang tepat untuk melakukannya: jsfiddle.net/Embnp . Lucunya, saya lupa menjawab pertanyaan ini =)
hellatan
@hellatan Anda dapat memperbaikinya dengan menyetel konstruktor: Mobil ke karena Anda menimpa properti prototipe dengan literal objek.
Josh Bedo
@ Astaga, terima kasih telah menunjukkan hal itu. Saya telah memperbarui jawaban saya sehingga saya tidak menimpa prototipe dengan literal objek, sebagaimana seharusnya dari awal.
hellatan
12

Letakkan fungsi pada objek prototipe saat Anda akan membuat banyak salinan dari jenis objek tertentu dan semuanya perlu berbagi perilaku yang sama. Dengan melakukan itu, Anda akan menghemat sebagian memori dengan hanya memiliki satu salinan dari setiap fungsi, tetapi itu hanya manfaat yang paling sederhana.

Mengubah metode pada objek prototipe, atau menambahkan metode, langsung mengubah sifat semua instance dari tipe yang sesuai.

Sekarang tepatnya mengapa Anda melakukan semua hal ini sebagian besar adalah fungsi dari desain aplikasi Anda sendiri, dan hal-hal yang perlu Anda lakukan dalam kode sisi klien. (Cerita yang sangat berbeda akan menjadi kode di dalam server; jauh lebih mudah membayangkan melakukan lebih banyak kode "OO" berskala besar di sana.)

Runcing
sumber
jadi ketika saya membuat contoh objek baru dengan metode prototipe (melalui kata kunci baru), maka objek itu tidak mendapatkan salinan baru dari setiap fungsi (hanya semacam penunjuk)? Jika ini masalahnya, mengapa Anda tidak ingin menggunakan prototipe?
opl
seperti @marcel, d'oh ... =)
hellatan
@ copy ya, kamu benar - tidak ada salinan yang dibuat. Sebaliknya, simbol (nama properti) pada objek prototipe secara alami "ada" sebagai bagian virtual dari setiap objek instance. Satu-satunya alasan orang tidak ingin repot dengan itu adalah kasus di mana objek berumur pendek dan berbeda, atau di mana tidak banyak "perilaku" untuk dibagikan.
Pointy
3

Jika saya jelaskan dalam istilah berbasis kelas maka Person adalah kelas, walk () adalah metode Prototipe. Jadi walk () hanya akan ada setelah Anda membuat instance objek baru dengan ini.

Jadi jika Anda ingin membuat salinan objek seperti Person u dapat membuat banyak pengguna Prototipe adalah solusi yang baik karena menghemat memori dengan berbagi / mewarisi salinan fungsi yang sama untuk setiap objek dalam memori.

Sedangkan statis tidak terlalu membantu dalam skenario seperti itu.

function Person(){
this.name = "anonymous";
}

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

Jadi dengan ini lebih seperti metode contoh. Pendekatan objek seperti metode Statis.

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

Anil Namde
sumber