Ketika Budi menulis object produk untuk katalog kafenya, ia hanya memasang dua properti: nama dan stok. Lalu suatu hari ia iseng mengetik produk.toString — dan alih-alih undefined, yang muncul adalah sebuah function. Padahal jelas ia tidak pernah menulis toString di dalam produk. Properti itu datang dari mana?

Pertanyaan Budi adalah pintu masuk ke konsep paling fundamental dari OOP JavaScript: prototype. Setiap object membawa tautan tersembunyi ke object lain, dan lewat tautan itu object bisa memakai properti yang tidak pernah ditulis padanya. Chapter ini mengajak kita memeriksa tautan tersebut, membandingkan dua cara pemeriksaan yang tersedia, dan membuktikan bahwa properti benar-benar bisa diwarisi antar object.

Aturan: Setiap Object Punya Prototype #

Aturannya begini: setiap object yang kita buat membawa tautan internal bernama [[Prototype]] yang menunjuk ke object lain. Ketika kita membaca sebuah properti, engine JavaScript memeriksa object itu dulu; bila propertinya tidak ditemukan, engine melanjutkan pencarian ke object di ujung tautan [[Prototype]].

Itulah mekanisme di balik temuan Budi. Object produk tidak memiliki toString, tetapi object di ujung tautannya punya. Engine tidak menyerah begitu properti tidak ditemukan di produk — pencarian dilanjutkan satu tingkat ke atas:

js
const produk = {
  nama: "Kopi Gayo",
  stok: 24,
};

console.log(produk.nama);
console.log(produk.toString);

Output yang Dihasilkan:

text
Kopi Gayo
[Function: toString]

Mari kita telaah:

  1. Baris 2–5 (const produk = {...}): object biasa dengan dua properti sendiri, nama dan stok. Tidak ada satu pun method yang kita tulis.
  2. Baris 7 (console.log(produk.nama)): properti ditemukan langsung di produk, sehingga nilainya tercetak tanpa pencarian tambahan.
  3. Baris 8 (console.log(produk.toString)): produk tidak punya toString, sehingga engine melompat ke [[Prototype]]-nya dan menemukan function di sana. [Function: toString] adalah bukti tautan itu bekerja.

Satu hal penting: [[Prototype]] adalah internal slot — sebuah jalur data di dalam engine, bukan properti biasa. Kita tidak bisa mengetik produk.[[Prototype]] untuk membacanya, karena sintaks tersebut tidak dikenal JavaScript. Karena itulah bahasa ini menyediakan dua cara resmi untuk mengintip tautan tersebut.

Object.getPrototypeOf dan proto #

JavaScript menyediakan dua cara memeriksa [[Prototype]] sebuah object. Cara modern dan direkomendasikan adalah Object.getPrototypeOf(objek). Cara klasik adalah membaca properti objek.__proto__ — sebuah getter yang tersedia di semua object, tetapi statusnya sudah lama ditandai usang untuk mengubah prototype.

AspekObject.getPrototypeOf(o)o.__proto__
BentukFunction utilitas milik ObjectGetter yang diwarisi semua object
StatusStandar resmi, direkomendasikanBekerja, tetapi ditandai usang
Mengubah prototypeGunakan Object.setPrototypeOfBisa, tetapi tidak disarankan
Kegunaan praktisPemeriksaan di kode productionPemeriksaan cepat, eksplorasi di console

Untuk membaca dan membandingkan — kebutuhan paling umum — keduanya memberi hasil yang sama. Berkas berikut membuktikannya langsung:

// pemeriksa-prototype.js
// Mengintip [[Prototype]]: sumber properti yang tidak kita tulis sendiri.

const produk = {
  nama: "Kopi Gayo",
  stok: 24,
};

console.log(produk.nama);
console.log(produk.stok);
console.log(produk.toString);

const protoProduk = Object.getPrototypeOf(produk);
console.log(protoProduk === Object.prototype);

console.log(produk.__proto__ === Object.prototype);
console.log(produk.__proto__ === protoProduk);

Output yang Dihasilkan:

text
Kopi Gayo
24
[Function: toString]
true
true
true

Bedah baris-baris pentingnya:

  1. Baris 11 (console.log(produk.toString)): function warisan tadi, kini tercetak sebagai bagian dari eksperimen utuh.
  2. Baris 13–14 (Object.getPrototypeOf(produk)): function utilitas mengembalikan object di ujung tautan [[Prototype]]. Perbandingan === Object.prototype menghasilkan true — object literal biasa diwariskan langsung dari Object.prototype.
  3. Baris 16 (produk.__proto__ === Object.prototype): getter klasik menunjuk ke object yang sama, sehingga perbandingannya juga true.
  4. Baris 17 (produk.__proto__ === protoProduk): kedua cara mengembalikan object yang identik, dibuktikan dengan === yang membandingkan identitas, bukan isi.

Dua Nama Tampak Serupa: [[Prototype]] dan .prototype #

Sampai titik ini dua istilah sering tercampur di kepala pemula, dan memisahkannya sekarang akan menyelamatkan kita dari kebingungan di chapter-chapter berikutnya. Yang pertama, [[Prototype]], adalah tautan internal yang dimiliki setiap object — jalur yang dilalui engine saat mencari properti. Yang kedua, .prototype, adalah properti biasa milik function dan class — isinya sebuah object yang kelak dijadikan [[Prototype]] untuk semua instance hasil new.

Perbedaannya bisa dibuktikan dalam empat baris:

js
function KartuMember() {}
console.log(typeof KartuMember.prototype);

const produk = {};
console.log(produk.prototype);

Output yang Dihasilkan:

text
object
undefined

Function memiliki properti .prototype yang isinya object — inilah cetakan tautan untuk instance-nya. Sebaliknya, object biasa seperti produk tidak punya properti bernama prototype sama sekali; yang dimiliki produk adalah [[Prototype]] yang hanya bisa diintip lewat Object.getPrototypeOf atau __proto__. Untuk memudahkan kembali ke pembedaannya:

IstilahMilik siapaIsinyaPerannya
[[Prototype]]Setiap objectTautan ke object lainJalur pencarian properti
.prototypeFunction dan classSebuah objectDijadikan [[Prototype]] bagi hasil new

Satu kecil tapi membantu: KartuMember.prototype adalah object biasa, dan ia sendiri punya [[Prototype]] — menunjuk ke Object.prototype. Rantai yang terbentuk itulah topik utuh chapter berikutnya.

Properti Warisan dengan Object.create #

Bukti paling gamblang bahwa properti bisa diwariskan: kita buat sendiri object yang berperan sebagai prototype, lalu kita buat object lain darinya. Function Object.create(proto) membuat object baru dengan [[Prototype]] yang menunjuk ke proto:

js
const dasarAlat = {
  sapa(nama) {
    return `Halo ${nama}, alat siap dipakai.`;
  },
};

const blender = Object.create(dasarAlat);
blender.merek = "Kencana";

console.log(blender.merek);
console.log(blender.sapa("Budi"));
console.log(blender.sapa === dasarAlat.sapa);
console.log(Object.getPrototypeOf(blender) === dasarAlat);

Output yang Dihasilkan:

text
Kencana
Halo Budi, alat siap dipakai.
true
true

Object blender hanya punya satu properti sendiri, yaitu merek. Method sapa ditulis hanya di dasarAlat, tetapi blender.sapa("Budi") tetap berjalan — engine tidak menemukan sapa di blender, lalu menemukannya satu tingkat di atas. Perbandingan blender.sapa === dasarAlat.sapa menghasilkan true: kedua nama menunjuk function yang sama, bukan salinan yang berbeda. Inilah pola berbagi yang kelak kita andalkan di chapter constructor function.

Kita sudah membuktikan pencarian satu tingkat ke atas. Tanda tanya yang tersisa bisa diuji sekarang: apa yang dikembalikan bila properti tidak ada di object mana pun sepanjang tautan?

js
const alatDapur = Object.create({ garansi: "12 bulan" });
console.log(alatDapur.garansi);
console.log(alatDapur.jaminan);

Output yang Dihasilkan:

text
12 bulan
undefined

garansi ditemukan di prototype yang kita tentukan sendiri, sedangkan jaminan tidak ada di alatDapur maupun di sepanjang tautannya — dan hasilnya undefined tanpa galat. Pertanyaan “sampai mana tautan itu berhenti” adalah inti dari prototype chain, dan itulah peta yang akan kita susun di chapter berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

pemeriksa-prototype.js

Unduh berkas
javascript
// pemeriksa-prototype.js
// Mengintip [[Prototype]]: sumber properti yang tidak kita tulis sendiri.

const produk = {
  nama: "Kopi Gayo",
  stok: 24,
};

console.log(produk.nama);
console.log(produk.stok);
console.log(produk.toString);

const protoProduk = Object.getPrototypeOf(produk);
console.log(protoProduk === Object.prototype);

console.log(produk.__proto__ === Object.prototype);
console.log(produk.__proto__ === protoProduk);