Dalam JavaScript kita hanya bisa mewarisi dari satu objek. Hanya ada satu [[Prototype]]
untuk sebuah objek. Dan suatu kelas hanya dapat memperluas satu kelas lainnya.
Namun terkadang hal itu terasa membatasi. Misalnya, kita mempunyai kelas StreetSweeper
dan kelas Bicycle
, dan ingin membuat campurannya: StreetSweepingBicycle
.
Atau kami memiliki kelas User
dan kelas EventEmitter
yang mengimplementasikan pembuatan acara, dan kami ingin menambahkan fungsionalitas EventEmitter
ke User
, sehingga pengguna kami dapat memancarkan acara.
Ada konsep yang dapat membantu di sini, yang disebut “mixin”.
Sebagaimana didefinisikan di Wikipedia, mixin adalah kelas yang berisi metode yang dapat digunakan oleh kelas lain tanpa perlu mewarisinya.
Dengan kata lain, mixin menyediakan metode yang mengimplementasikan perilaku tertentu, namun kita tidak menggunakannya sendiri, kita menggunakannya untuk menambahkan perilaku ke kelas lain.
Cara paling sederhana untuk mengimplementasikan mixin dalam JavaScript adalah dengan membuat objek dengan metode yang berguna, sehingga kita dapat dengan mudah menggabungkannya menjadi prototipe kelas mana pun.
Misalnya di sini mixin sayHiMixin
digunakan untuk menambahkan beberapa “ucapan” untuk User
:
// campur katakanlahHiMixin = { ucapkan Hai() { alert(`Halo ${nama ini}`); }, ucapkan selamat tinggal() { alert(`Sampai jumpa ${nama ini}`); } }; // penggunaan: pengguna kelas { konstruktor(nama) { ini.nama = nama; } } // salin metodenya Objek.penetapan(Pengguna.prototipe, sayHiMixin); // sekarang Pengguna dapat menyapa Pengguna baru("Bung").sayHi(); // Halo kawan!
Tidak ada warisan, tetapi metode penyalinan yang sederhana. Jadi User
dapat mewarisi dari kelas lain dan juga menyertakan mixin untuk “mencampur” metode tambahan, seperti ini:
kelas Pengguna memperluas Orang { // ... } Objek.penetapan(Pengguna.prototipe, sayHiMixin);
Mixin dapat memanfaatkan warisan di dalam dirinya.
Misalnya, di sini sayHiMixin
mewarisi dari sayMixin
:
misalkanMixin = { katakan(frasa) { peringatan(frasa); } }; katakanlahHiMixin = { __proto__: sayMixin, // (atau kita bisa menggunakan Object.setPrototypeOf untuk menyetel prototipe di sini) ucapkan Hai() { // memanggil metode induk super.say(`Halo ${ini.nama}`); // (*) }, ucapkan selamat tinggal() { super.say(`Sampai jumpa ${ini.nama}`); // (*) } }; pengguna kelas { konstruktor(nama) { ini.nama = nama; } } // salin metodenya Objek.penetapan(Pengguna.prototipe, sayHiMixin); // sekarang Pengguna dapat menyapa Pengguna baru("Bung").sayHi(); // Halo kawan!
Harap perhatikan bahwa panggilan ke metode induk super.say()
dari sayHiMixin
(pada baris berlabel (*)
) mencari metode dalam prototipe mixin tersebut, bukan kelasnya.
Berikut diagramnya (lihat bagian kanan):
Itu karena metode sayHi
dan sayBye
awalnya dibuat di sayHiMixin
. Jadi meskipun disalin, properti internal [[HomeObject]]
mereka merujuk pada sayHiMixin
, seperti yang ditunjukkan pada gambar di atas.
Saat super
mencari metode induk di [[HomeObject]].[[Prototype]]
, itu berarti ia mencari sayHiMixin.[[Prototype]]
.
Sekarang mari kita membuat mixin untuk kehidupan nyata.
Fitur penting dari banyak objek browser (misalnya) adalah objek tersebut dapat menghasilkan peristiwa. Acara adalah cara terbaik untuk “menyiarkan informasi” kepada siapa saja yang menginginkannya. Jadi mari kita membuat mixin yang memungkinkan kita dengan mudah menambahkan fungsi terkait event ke kelas/objek apa pun.
Mixin akan menyediakan metode .trigger(name, [...data])
untuk “menghasilkan suatu peristiwa” ketika sesuatu yang penting terjadi padanya. Argumen name
adalah nama peristiwa, secara opsional diikuti dengan argumen tambahan dengan data peristiwa.
Juga metode .on(name, handler)
yang menambahkan fungsi handler
sebagai pendengar event dengan nama yang diberikan. Ini akan dipanggil ketika suatu peristiwa dengan name
tertentu terpicu, dan mendapatkan argumen dari panggilan .trigger
.
…Dan metode .off(name, handler)
yang menghapus pendengar handler
.
Setelah menambahkan mixin, user
objek akan dapat menghasilkan acara "login"
ketika pengunjung masuk. Dan objek lain, misalnya, calendar
mungkin ingin mendengarkan acara tersebut untuk memuat kalender bagi orang yang masuk.
Atau, menu
dapat menghasilkan peristiwa "select"
ketika item menu dipilih, dan objek lain dapat menugaskan penangan untuk bereaksi terhadap peristiwa tersebut. Dan sebagainya.
Berikut kodenya:
biarkan eventMixin = { /** * Berlangganan acara, penggunaan: * menu.on('pilih', fungsi(item) { ... } */ pada(nama kejadian, pengendali) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[namaacara] = []; } this._eventHandlers[eventName].push(handler); }, /** * Batalkan langganan, penggunaan: * menu.off('pilih', pengendali) */ off(nama kejadian, pengendali) { biarkan penangan = ini._eventHandlers?.[eventName]; if (!handlers) kembali; for (misalkan i = 0; i < handlers.length; i++) { if (penangan[i] === penangan) { handlers.splice(i--, 1); } } }, /** * Hasilkan acara dengan nama dan data yang diberikan * this.trigger('pilih', data1, data2); */ pemicu(nama kejadian, ...args) { if (!this._eventHandlers?.[eventName]) { kembali; // tidak ada penangan untuk nama event tersebut } // panggil penangannya this._eventHandlers[eventName].forEach(handler => handler.apply(ini, args)); } };
.on(eventName, handler)
– menetapkan fungsi handler
untuk dijalankan ketika peristiwa dengan nama tersebut terjadi. Secara teknis, ada properti _eventHandlers
yang menyimpan array penangan untuk setiap nama peristiwa, dan hanya menambahkannya ke daftar.
.off(eventName, handler)
– menghapus fungsi dari daftar handler.
.trigger(eventName, ...args)
– menghasilkan acara: semua penangan dari _eventHandlers[eventName]
dipanggil, dengan daftar argumen ...args
.
Penggunaan:
// Buat kelas Menu kelas { pilih(nilai) { this.trigger("pilih", nilai); } } // Tambahkan mixin dengan metode yang berhubungan dengan event Objek.penetapan(Menu.prototipe, eventMixin); biarkan menu = Menu baru(); // menambahkan handler, untuk dipanggil saat seleksi: menu.on("pilih", value => alert(`Nilai yang dipilih: ${value}`)); // memicu event => handler di atas berjalan dan menampilkan: // Nilai yang dipilih: 123 menu.pilih("123");
Sekarang, jika kita ingin kode bereaksi terhadap pilihan menu, kita dapat mendengarkannya dengan menu.on(...)
.
Dan mixin eventMixin
memudahkan untuk menambahkan perilaku seperti itu ke sebanyak mungkin kelas yang kita inginkan, tanpa mengganggu rantai pewarisan.
Mixin – adalah istilah umum pemrograman berorientasi objek: kelas yang berisi metode untuk kelas lain.
Beberapa bahasa lain mengizinkan pewarisan berganda. JavaScript tidak mendukung pewarisan berganda, namun mix dapat diimplementasikan dengan menyalin metode ke dalam prototipe.
Kita bisa menggunakan mixin sebagai cara untuk menambah kelas dengan menambahkan beberapa perilaku, seperti penanganan event seperti yang telah kita lihat di atas.
Mixin dapat menjadi titik konflik jika secara tidak sengaja menimpa metode kelas yang ada. Jadi secara umum seseorang harus memikirkan dengan baik metode penamaan mixin, untuk meminimalkan kemungkinan hal itu terjadi.