Kenapa ini penting
Coba jalankan [1, 2, 3].map(...). Kamu tidak pernah menulis method map di array itu, tapi method-nya ada. Dari mana asalnya? Jawabannya adalah prototype. Setiap object di JavaScript punya "orang tua" tersembunyi. Kalau sebuah properti tidak ditemukan di object itu sendiri, JavaScript mencarinya ke orang tuanya, lalu ke orang tua dari orang tuanya, dan seterusnya. Rantai pencarian ini disebut prototype chain.
Analogi sehari-hari: kamu bertanya resep rendang ke adikmu. Adikmu tidak tahu, jadi ia bertanya ke ibu. Ibu juga tidak hafal, lalu bertanya ke nenek. Nenek tahu, dan jawabannya sampai ke kamu. Kalau sampai nenek pun tidak tahu (ujung rantai), jawabannya "tidak ada" (undefined).
Memahami prototype membuat kamu paham apa yang sebenarnya terjadi saat menulis class, kenapa method dibagikan antar-object tanpa memakan memori berlebih, dan bagaimana membaca pesan error seperti x.foo is not a function. Ini salah satu konsep yang membedakan programmer yang "bisa pakai" dengan yang "benar-benar paham" JavaScript.
Konsep inti
[[Prototype]]: tautan tersembunyi
Setiap object punya slot internal [[Prototype]] yang menunjuk ke object lain atau null. Cara yang benar untuk membaca dan mengaturnya:
Object.getPrototypeOf(obj)untuk membaca.Object.setPrototypeOf(obj, proto)untuk mengubah (lambat, hindari di kode yang sering jalan).Object.create(proto)untuk membuat object baru dengan prototype tertentu sejak awal (cara yang disarankan).
__proto__ vs prototype
Dua nama ini mirip tapi sangat berbeda, dan sering membingungkan:
obj.__proto__ |
Fungsi.prototype |
|
|---|---|---|
| Dimiliki oleh | Semua object | Function biasa dan class |
| Artinya | Getter/setter lama untuk [[Prototype]] object itu |
Object yang akan menjadi [[Prototype]] bagi object yang dibuat dengan new Fungsi() |
| Status | Warisan lama, sebaiknya pakai Object.getPrototypeOf |
Bagian inti bahasa, dipakai terus |
Kalimat kuncinya: new Fungsi() menghasilkan object yang [[Prototype]]-nya adalah Fungsi.prototype. Jadi Object.getPrototypeOf(new Fungsi()) === Fungsi.prototype bernilai true.
Cara kerja pencarian properti
Saat kamu menulis obj.x:
- Apakah
objpunya properti sendiri (own property) bernamax? Kalau ya, pakai. - Kalau tidak, cari di
Object.getPrototypeOf(obj). - Ulangi sampai ketemu atau sampai prototype bernilai
null, lalu hasilnyaundefined.
Penulisan (obj.x = 1) berbeda: biasanya selalu membuat properti sendiri di obj, tidak mengubah prototype. Ini disebut shadowing, yaitu properti di object menutupi properti dengan nama sama di prototype.
Rantai bawaan
[1,2,3] → Array.prototype → Object.prototype → null
{a: 1} → Object.prototype → null
Itulah sebabnya array punya map (dari Array.prototype) dan juga hasOwnProperty (dari Object.prototype).
Own property vs inherited
Object.hasOwn(obj, "x")(modern) atauobj.hasOwnProperty("x")mengecek properti milik sendiri saja."x" in objmengecek properti sendiri dan warisan.Object.keyshanya properti sendiri yang enumerable;for...inikut menampilkan properti warisan yang enumerable.
Contoh kode
// 1. Object.create: buat object dengan "orang tua" tertentu
const hewan = {
bernapas() { return `${this.nama} bernapas`; },
makan(makanan) { return `${this.nama} makan ${makanan}`; },
};
const kucing = Object.create(hewan);
kucing.nama = "Oyen";
kucing.bersuara = function () { return "Meong!"; };
console.log(kucing.bernapas()); // → Oyen bernapas (dari prototype)
console.log(kucing.bersuara()); // → Meong! (milik sendiri)
console.log(Object.getPrototypeOf(kucing) === hewan); // → true
// 2. Own vs inherited
console.log(Object.hasOwn(kucing, "nama")); // → true
console.log(Object.hasOwn(kucing, "bernapas")); // → false
console.log("bernapas" in kucing); // → true
console.log(Object.keys(kucing)); // → [ 'nama', 'bersuara' ]
// 3. Shadowing: tulis ke object, prototype tidak berubah
kucing.makan = () => "Oyen makan ikan (versi khusus)";
console.log(kucing.makan()); // → Oyen makan ikan (versi khusus)
console.log(hewan.makan.call({ nama: "Belang" }, "daging")); // → Belang makan daging
// 4. Rantai lebih panjang
const anakKucing = Object.create(kucing);
anakKucing.nama = "Cimol";
console.log(anakKucing.bersuara()); // → Meong! (dari kucing)
console.log(anakKucing.bernapas()); // → Cimol bernapas (dari hewan, this = anakKucing)
// 5. Fungsi konstruktor dan .prototype
function Produk(nama, harga) {
this.nama = nama; // properti per object
this.harga = harga;
}
Produk.prototype.label = function () { // method dibagikan semua instance
return `${this.nama} - Rp${this.harga.toLocaleString("id-ID")}`;
};
const p1 = new Produk("Buku", 85000);
const p2 = new Produk("Pena", 5000);
console.log(p1.label()); // → Buku - Rp85.000
console.log(p1.label === p2.label); // → true (satu function, hemat memori)
console.log(Object.getPrototypeOf(p1) === Produk.prototype); // → true
console.log(Produk.prototype.constructor === Produk); // → true
// 6. Melihat rantai bawaan
const arr = [1, 2, 3];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // → true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // → true
console.log(Object.getPrototypeOf(Object.prototype)); // → null
// 7. Fungsi bantu: cetak seluruh rantai
function rantai(obj) {
const hasil = [];
let p = Object.getPrototypeOf(obj);
while (p) {
hasil.push(p.constructor?.name ?? "(tanpa nama)");
p = Object.getPrototypeOf(p);
}
return hasil.join(" → ") + " → null";
}
console.log(rantai(p1)); // → Produk → Object → null
console.log(rantai(arr)); // → Array → Object → null
// 8. Object tanpa prototype: cocok untuk "kamus" murni
const kamus = Object.create(null);
kamus.apel = "apple";
console.log("toString" in kamus); // → false (tidak mewarisi apa pun)
Poin penting dari contoh 4: meskipun method bernapas berada di hewan, this di dalamnya menunjuk ke object yang memanggil (anakKucing), bukan object tempat method itu disimpan. Inilah yang membuat satu method bisa dipakai bersama oleh banyak object.
Kesalahan umum
- Menyamakan
__proto__denganprototype.__proto__ada di semua object (menunjuk orang tuanya),prototypehanya ada di function/class (cetak biru untuk anak yang dibuat dengannew). - Memakai arrow function sebagai method prototype. Arrow function tidak punya
thissendiri, jadithis.namatidak menunjuk ke instance. Gunakan function biasa atau sintaks method. - Menaruh array/object di prototype sebagai data.
Proto.prototype.items = []membuat semua instance berbagi array yang sama. Data per-instance taruh di konstruktor. - Mengubah prototype bawaan seperti
Array.prototype.sum = .... Ini bisa bentrok dengan library atau versi JavaScript mendatang. Buat function terpisah saja. - Mengandalkan
for...inuntuk data. Ia ikut menampilkan properti warisan yang enumerable. PakaiObject.keys/Object.entries.
Latihan
- Mudah, Buat object
kendaraandengan methodjalan()yang mengembalikan"<merk> berjalan". BuatmotormemakaiObject.create(kendaraan), isimerk, lalu panggiljalan(). - Sedang, Buat fungsi konstruktor
Siswa(nama, nilai)dengan method di prototypelulus()(true jika nilai >= 75). Buktikan dua instance berbagi method yang sama. - Menantang, Tulis function
cariPemilik(obj, kunci)yang mengembalikan object di rantai prototype tempat propertikuncibenar-benar didefinisikan (ataunull).
Lihat solusi
// 1
const kendaraan = { jalan() { return `${this.merk} berjalan`; } };
const motor = Object.create(kendaraan);
motor.merk = "Vario";
console.log(motor.jalan()); // → Vario berjalan
// 2
function Siswa(nama, nilai) { this.nama = nama; this.nilai = nilai; }
Siswa.prototype.lulus = function () { return this.nilai >= 75; };
const s1 = new Siswa("Andi", 80), s2 = new Siswa("Bela", 60);
console.log(s1.lulus(), s2.lulus(), s1.lulus === s2.lulus); // → true false true
// 3
function cariPemilik(obj, kunci) {
let p = obj;
while (p !== null) {
if (Object.hasOwn(p, kunci)) return p;
p = Object.getPrototypeOf(p);
}
return null;
}
console.log(cariPemilik(motor, "jalan") === kendaraan); // → true
console.log(cariPemilik([], "map") === Array.prototype); // → true
console.log(cariPemilik({}, "tidakAda")); // → null
Ringkasan
- Setiap object punya
[[Prototype]]; properti yang tidak ditemukan dicari ke atas sepanjang prototype chain sampainull. - Baca dengan
Object.getPrototypeOf, buat denganObject.create;__proto__adalah cara lama. Fungsi.prototypemenjadi prototype bagi object hasilnew Fungsi().- Method di prototype dibagikan semua instance,
thistetap menunjuk object pemanggil. - Bedakan own property (
Object.hasOwn) dengan warisan (in).