Bab 7: Prototype & Inheritance
7.1 Prototypal Inheritance
Bayangin kamu punya buku resep keluarga. Kalau kamu mau masak dan resepnya tidak ada di buku pribadimu, kamu buka buku resep orang tua. Kalau di situ juga tidak ada, buka buku resep nenek. Itulah prototype chain — rantai pencarian "warisan" dari satu objek ke objek lain.
Konsep [[Prototype]]
Setiap objek di JavaScript punya properti tersembunyi [[Prototype]] yang menunjuk ke objek lain (atau null). Saat kamu akses properti yang tidak ada di objek, JavaScript otomatis cari di prototype-nya.
const hewan = {
makan: true,
jalan() {
console.log("Jalan-jalan...");
}
};
const kelinci = {
lompat: true,
__proto__: hewan // Set prototype kelinci = hewan
};
console.log(kelinci.makan); // true (dari hewan)
console.log(kelinci.lompat); // true (milik sendiri)
kelinci.jalan(); // "Jalan-jalan..." (dari hewan)Rantai Prototype Bisa Panjang
const hewan = {
makan: true,
jalan() { console.log("Jalan..."); }
};
const kelinci = {
lompat: true,
__proto__: hewan
};
const kelinciKecil = {
panjangTelinga: 5,
__proto__: kelinci
};
kelinciKecil.jalan(); // "Jalan..." (dari hewan, lewat kelinci)
console.log(kelinciKecil.lompat); // true (dari kelinci)Rantai:
kelinciKecil→kelinci→hewan→Object.prototype→null
Menulis Tidak Pakai Prototype
Prototype hanya dipakai untuk membaca. Kalau kamu menulis properti, langsung disimpan di objek itu sendiri:
const hewan = {
jalan() {
console.log("Hewan jalan");
}
};
const kelinci = {
__proto__: hewan
};
// Menulis method baru langsung di kelinci
kelinci.jalan = function() {
console.log("Kelinci lompat-lompat!");
};
kelinci.jalan(); // "Kelinci lompat-lompat!" (milik sendiri)Nilai this Selalu Objek Sebelum Titik
Ini penting banget! Mau method-nya dari prototype mana pun, this selalu menunjuk ke objek yang memanggil:
const hewan = {
tidur() {
this.sedangTidur = true;
}
};
const kelinci = {
nama: "Kelinci Putih",
__proto__: hewan
};
kelinci.tidur(); // this = kelinci
console.log(kelinci.sedangTidur); // true
console.log(hewan.sedangTidur); // undefined (hewan tidak terpengaruh!)for..in Iterasi Properti Warisan Juga
const hewan = { makan: true };
const kelinci = { lompat: true, __proto__: hewan };
// Object.keys hanya milik sendiri
console.log(Object.keys(kelinci)); // ["lompat"]
// for..in termasuk warisan
for (let prop in kelinci) {
console.log(prop); // "lompat", lalu "makan"
}
// Filter hanya milik sendiri:
for (let prop in kelinci) {
if (kelinci.hasOwnProperty(prop)) {
console.log(`Milik sendiri: ${prop}`); // "lompat"
}
}__proto__itu getter/setter, bukan properti biasa. Dia cara lama untuk akses[[Prototype]]. Cara modern:Object.getPrototypeOf()/Object.setPrototypeOf().- Prototype chain tidak boleh melingkar. JavaScript akan error kalau A → B → A.
- Hanya boleh satu prototype. Objek tidak bisa inherit dari dua objek sekaligus.
- Hati-hati dengan properti array/objek di prototype! Kalau prototype punya array, semua "anak" akan berbagi array yang sama (bukan salinan).
// BUG: Semua hamster berbagi perut yang sama!
const hamster = {
perut: [],
makan(makanan) {
this.perut.push(makanan); // push ke prototype!
}
};
const hamster1 = { __proto__: hamster };
const hamster2 = { __proto__: hamster };
hamster1.makan("apel");
console.log(hamster2.perut); // ["apel"] — Lho?!
// FIX: Setiap hamster punya perut sendiri
const hamster1Fix = { __proto__: hamster, perut: [] };
const hamster2Fix = { __proto__: hamster, perut: [] };Buat 3 objek: kendaraan, mobil, sedan dengan rantai prototype:
kendaraanpunya methodklakson()yang print "BEEP!"mobilinherit darikendaraan, punya propertiroda: 4sedaninherit darimobil, punya propertipintu: 4
Test: sedan.klakson() harus print "BEEP!" dan sedan.roda harus 4.
7.2 F.prototype
Kalau __proto__ itu cara manual set warisan, F.prototype itu cara otomatis — setiap kali kamu pakai new, JavaScript otomatis set [[Prototype]] objek baru ke F.prototype.
Cara Kerja
const hewan = {
makan: true
};
function Kelinci(nama) {
this.nama = nama;
}
Kelinci.prototype = hewan;
const kelinci = new Kelinci("Putih");
// kelinci.__proto__ === hewan
console.log(kelinci.makan); // true
F.prototypehanya dipakai saatnew F()dipanggil. KalauF.prototypeberubah setelahnya, objek yang sudah dibuat tidak terpengaruh.
Default: constructor
Setiap fungsi punya prototype default berisi { constructor: F }:
function Kelinci() {}
// Default:
// Kelinci.prototype = { constructor: Kelinci }
console.log(Kelinci.prototype.constructor === Kelinci); // true
const k = new Kelinci();
console.log(k.constructor === Kelinci); // true (dari prototype)Ini berguna kalau kamu punya objek tapi tidak tahu constructor-nya:
const k2 = new k.constructor("Hitam"); // Bikin kelinci baru!Kalau kamu replace seluruh prototype, constructor hilang!
function Kelinci() {}
// SALAH: constructor hilang
Kelinci.prototype = {
lompat: true
};
const k = new Kelinci();
console.log(k.constructor === Kelinci); // false!
// BENAR: tambahkan ke prototype yang ada
Kelinci.prototype.lompat = true;
// atau set ulang constructor:
Kelinci.prototype = {
lompat: true,
constructor: Kelinci
};Buat constructor function Mobil(merk) yang:
- Set
this.merk = merk - Punya
Mobil.prototype.info()yang return"Mobil: [merk]" - Pastikan
constructortetap benar setelah menambah method
7.3 Native Prototypes
Pernah heran kenapa array punya .map(), .filter(), .push()? Padahal kamu tidak pernah bikin method itu. Jawabannya: semua method itu tinggal di Array.prototype, dan setiap array yang kamu buat otomatis mewarisinya.
Rantai Built-in
[1, 2, 3] → Array.prototype → Object.prototype → null
"hello" → String.prototype → Object.prototype → null
function() → Function.prototype → Object.prototype → null
const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__); // nullPrimitif Juga Punya Prototype
String, number, boolean — saat kamu akses method-nya, JavaScript bikin "wrapper object" sementara:
// Ini bisa karena String.prototype.toUpperCase ada
console.log("hello".toUpperCase()); // "HELLO"
// null dan undefined TIDAK punya prototypeMenambah Method ke Native Prototype (Polyfill)
// Polyfill: tambah method yang belum ada di engine lama
if (!String.prototype.repeat) {
String.prototype.repeat = function(n) {
return new Array(n + 1).join(this);
};
}
console.log("ha".repeat(3)); // "hahaha"PERINGATAN: Jangan sembarangan modifikasi native prototype! Ini bisa konflik dengan library lain. Satu-satunya alasan yang valid: polyfill (menambah fitur standar yang belum didukung engine).
Meminjam Method (Borrowing)
// Objek array-like (bukan array asli)
const obj = {
0: "Hello",
1: "World",
length: 2
};
// Pinjam method join dari Array.prototype
obj.join = Array.prototype.join;
console.log(obj.join(", ")); // "Hello, World"- Jangan modifikasi
Object.prototype! Semua objek mewarisinya — efeknya global dan berbahaya. - Native prototype bisa di-override. Kalau dua library sama-sama menambah
String.prototype.trim, yang terakhir menang.
Tambahkan method last() ke Array.prototype yang mengembalikan elemen terakhir array:
// Setelah kamu tambahkan:
console.log([1, 2, 3].last()); // 3
console.log(["a", "b"].last()); // "b"7.4 Prototype Methods & Objects Tanpa proto
Cara Modern (Rekomendasi)
// Buat objek dengan prototype tertentu
const hewan = { makan: true };
const kelinci = Object.create(hewan);
console.log(kelinci.makan); // true
console.log(Object.getPrototypeOf(kelinci) === hewan); // true
// Ubah prototype (jarang dipakai, lambat!)
Object.setPrototypeOf(kelinci, {});Object.create dengan Descriptor
const hewan = { makan: true };
const kelinci = Object.create(hewan, {
lompat: {
value: true,
writable: true,
enumerable: true,
configurable: true
}
});
console.log(kelinci.lompat); // true
console.log(kelinci.makan); // trueClone Sempurna (Termasuk Flag & Prototype)
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);Objek "Sangat Polos" (Tanpa Prototype)
Berguna untuk dictionary/map yang aman dari key __proto__:
// Objek biasa: __proto__ itu "spesial"
const biasa = {};
biasa["__proto__"] = "test";
console.log(biasa["__proto__"]); // [object Object] — BUKAN "test"!
// Objek tanpa prototype: aman
const dict = Object.create(null);
dict["__proto__"] = "test";
console.log(dict["__proto__"]); // "test" — works!
// Tapi tidak punya toString, hasOwnProperty, dll
// Object.keys tetap works karena itu static method
console.log(Object.keys(dict)); // ["__proto__"]- Jangan ubah prototype setelah objek dibuat kecuali benar-benar perlu. Ini sangat lambat karena merusak optimisasi internal engine.
Object.create(null)tidak punya method bawaan. Tidak bisatoString(),hasOwnProperty(), dll.
Buat "dictionary" yang aman menggunakan Object.create(null):
- Tambahkan method
toStringyang non-enumerable (tidak muncul difor..in) toStringharus return semua key yang dipisahkan koma
// Harusnya:
dict.apple = "Apel";
dict.__proto__ = "test"; // Harus tersimpan sebagai data biasa!
for (let key in dict) console.log(key); // "apple", "__proto__"
console.log(dict.toString()); // "apple,__proto__"
// toString TIDAK muncul di loopSudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.