Belajar dengan membaca secara berurutan

Prototype — Apa yang Ada di Balik Class

Membahas prototype dan prototype chain: Object.getPrototypeOf, perbedaan hasOwnProperty dan in, serta cara menambah method belakangan.

Kursus yang dibuat dari class bisa memanggil summary(), padahal instance itu sendiri tidak punya method tersebut. Kalau kamu tidak tahu di mana method itu disimpan, kamu juga tidak bisa menjelaskan kenapa child class bisa memanggil method milik parent class.

Artikel ini membahas prototype, tempat method class disimpan, dan prototype chain, urutan yang dipakai JavaScript untuk mencari sebuah nama.

Memanggil Method yang Tidak Dimiliki Instance — prototype

Kalau kamu membuat 300 kursus, mungkin kamu bertanya-tanya apakah body summary juga ikut dibuat 300 kali, satu untuk tiap instance. Object.keys(jsCourse) tidak menampilkan summary, jadi dari situ kamu tidak bisa tahu di mana method yang kamu panggil itu disimpan.

Prototype (object lain tempat sebuah object mewarisi property) bisa diambil dengan Object.getPrototypeOf(), dan untuk jsCourse hasilnya adalah Course.prototype. Nama yang tidak dimiliki object itu sendiri dicari di sana.

Property prototype milik class juga merujuk ke object yang sama, tapi jsCourse.prototype bernilai undefined.

class Course {
  constructor(title, hours) {
    this.title = title;   // Nama kursus
    this.hours = hours;   // Jam belajar
  }
  summary() { return `${this.title} (${this.hours} jam)`; }
}
const jsCourse = new Course("JS Dasar", 12);
const sqlCourse = new Course("SQL Dasar", 8);

// summary tidak termasuk key milik instance itu sendiri
console.log(Object.keys(jsCourse).join(", "));                       // title, hours

// Prototype jsCourse adalah Course.prototype (bukan jsCourse.prototype)
console.log(Object.getPrototypeOf(jsCourse) === Course.prototype);   // true
console.log(jsCourse.prototype);                                     // undefined

// Kedua kursus memakai satu summary yang sama di Course.prototype
console.log(jsCourse.summary === sqlCourse.summary);                 // true
console.log(sqlCourse.summary());                                    // SQL Dasar (8 jam)
Yang Dirujuk Class dan Instance
new Course("JS Dasar", 12)class Coursenew Course("SQL Dasar", 8)jsCourseCourse.prototypepunya summarysqlCourseKey sendiri:title, hoursBody summaryhanya satuTidak punya, jadicari diCourse.prototypejsCourse.prototypeadalah undefinedKedua summaryidentik (true)summary() →SQL Dasar(8 jam)
Prototype kedua kursus sama dengan prototype milik class. summary, yang tidak mereka miliki sendiri, ditemukan di sana.

Body-nya hanya satu, tapi this diisi dengan apa pun yang ada di sebelah kiri titik pada baris pemanggil, jadi sqlCourse.summary() menyusun string-nya dari value milik sqlCourse. Meskipun ada 300 kursus, yang bertambah hanyalah instance yang menyimpan title dan hours.

Mencari Berurutan Sampai null — Prototype Chain

Kalau kamu membuat kursus live streaming dengan class LiveCourse extends Course, liveCourse.summary() juga berjalan. Padahal satu-satunya method yang ditulis di prototype liveCourse, yaitu LiveCourse.prototype, hanyalah schedule.

Nama dicari di sepanjang prototype chain (rangkaian referensi yang kamu dapat dengan menelusuri prototype dari prototype sampai null), mulai dari instance. extends menjadikan Course.prototype sebagai mata rantai berikutnya setelah LiveCourse.prototype, dan setelah itu ada Object.prototype (object bawaan yang berada di ujung chain).

class Course {
  constructor(title, hours) { this.title = title; this.hours = hours; }
  summary() { return `${this.title} (${this.hours} jam)`; }
}
class LiveCourse extends Course { schedule() { return "Setiap Selasa mulai pukul 20.00"; } }
const liveCourse = new LiveCourse("React Praktis", 10);

// Ambil prototype-nya satu tingkat demi satu tingkat
const step1 = Object.getPrototypeOf(liveCourse);
const step2 = Object.getPrototypeOf(step1);
const step3 = Object.getPrototypeOf(step2);
console.log(step1 === LiveCourse.prototype);   // true
console.log(step2 === Course.prototype);       // true
console.log(step3 === Object.prototype);       // true
console.log(Object.getPrototypeOf(step3));     // null (ujung chain)

// summary ditemukan di tingkat kedua, Course.prototype
console.log(liveCourse.summary());             // React Praktis (10 jam)
Urutan Pencarian summary di Chain
liveCourse(instance)Punya title, hoursTidak ada summaryLiveCourse.prototypePunya scheduleTidak ada summaryCourse.prototypePunya summaryDitemukan di siniObject.prototypePunya methodbawaan(berikutnya null)
summary dicari dari atas dan ditemukan di Course.prototype. Chain berakhir di null, tepat setelah Object.prototype.

Yang masuk ke chain adalah Course.prototype, bukan Course itu sendiri. Method yang diberi static disimpan di Course, jadi kalau dibaca dari instance, pencariannya sampai ke null tanpa menemukannya dan hasilnya undefined; kalau dipanggil, hasilnya TypeError.

Key Bernama Sama di Instance Ditemukan Lebih Dulu

Kalau kamu meng-assign liveCourse.summary = "Segera hadir";, nama itu ditemukan di liveCourse sendiri, dan prototype tidak pernah dicari. Akibatnya, liveCourse.summary() melempar TypeError: liveCourse.summary is not a function.

Tampilkan urutan pencarian method untuk tiap jenis mobil. Car, ElectricCar, compactCar, ev, dan prototypeNames sudah dideklarasikan.

① Tampilkan apakah prototype ev adalah ElectricCar.prototype.

② Definisikan chainOf, yang menggabungkan nama-nama mulai dari prototype mobil yang diberikan sampai tepat sebelum null.

③ Tampilkan hasilnya untuk compactCar dan ev.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Membatasi Pengecekan ke Key Milik Instance Sendiri — hasOwnProperty

Misalnya kamu menerima data kursus dan ingin memeriksa apakah summary ditimpa di kursus tertentu lewat assignment langsung. "summary" in jsCourse bernilai true bahkan untuk method yang ada di prototype, jadi cara ini tidak bisa membedakan apakah value itu di-assign langsung ke instance.

hasOwnProperty (method yang mengembalikan true hanya kalau object itu sendiri punya key tersebut) menerima key dalam bentuk string, seperti jsCourse.hasOwnProperty("title"). Method ini sendiri berada di Object.prototype dan ditemukan lewat chain.

class Course {
  constructor(title, hours) { this.title = title; this.hours = hours; }
  summary() { return `${this.title} (${this.hours} jam)`; }
}
const jsCourse = new Course("JS Dasar", 12);
const sqlCourse = new Course("SQL Dasar", 8);
sqlCourse.summary = "Segera hadir";   // Timpa hanya untuk kursus ini

// in bernilai true meskipun namanya ada di prototype
console.log("summary" in jsCourse);                        // true
console.log("summary" in sqlCourse);                       // true

// hasOwnProperty bernilai true hanya untuk key milik kursus itu sendiri
console.log(jsCourse.hasOwnProperty("summary"));           // false
console.log(sqlCourse.hasOwnProperty("summary"));          // true (sudah ditimpa)
console.log(Course.prototype.hasOwnProperty("summary"));   // true
Jangkauan in dan hasOwnProperty
Dicari oleh "summary" in jsCourse (seluruh chain)
jsCourse sendiri (dicek oleh hasOwnProperty)
  • title"JS Dasar"
  • hours12
Course.prototype
  • summary()true dengan in, false dengan hasOwnProperty
Object.prototype
  • hasOwnProperty()jsCourse bisa memanggilnya karena method ini ditemukan di sini
in melihat seluruh chain, sedangkan hasOwnProperty hanya melihat kotak jsCourse. summary tidak ada di kotak jsCourse.

Untuk sqlCourse, yang ditimpa lewat assignment, hasOwnProperty("summary") mengembalikan true, jadi satu baris sudah cukup untuk menemukan kursus yang ditimpa saja. Tabel di bawah menunjukkan bagaimana cara pengecekan lain memperlakukan title dan summary.

Cara cektitle (milik jsCourse)summary (di Course.prototype)
Muncul di Object.keys(jsCourse)?YaTidak
Muncul di for...in?YaTidak
Course.prototype.hasOwnProperty("key")falsetrue

Menambah Method dari Luar — Assignment ke prototype

Sekarang anggap instance kursus dibuat oleh kode bersama milik tim lain lalu diserahkan kepadamu, jadi kamu tidak bisa mengubah body class maupun baris new-nya. Kalau function tampilan yang mengembalikan "Kursus: nama" kamu assign ke tiap kursus satu per satu, kursus yang terlewat akan melempar TypeError saat function itu dipanggil.

Kalau kamu meng-assign function ke prototype, seperti Course.prototype.label = function () { ... }, setiap instance menemukan label yang sama lewat chain. Kalau body class-nya bisa kamu ubah, tulis method-nya di sana saja.

class Course {
  constructor(title, hours) { this.title = title; this.hours = hours; }
  summary() { return `${this.title} (${this.hours} jam)`; }
}
const jsCourse = new Course("JS Dasar", 12);   // Buat dua kursus lebih dulu
const sqlCourse = new Course("SQL Dasar", 8);

// Kalau hanya di-assign ke jsCourse, sqlCourse tidak punya label
jsCourse.label = function () { return `Kursus: ${this.title}`; };
console.log(typeof sqlCourse.label);                // undefined (kalau dipanggil, melempar TypeError)

// Assign sekali ke Course.prototype (ditulis dengan function karena membaca this)
Course.prototype.label = function () { return `Kursus: ${this.title}`; };
console.log(sqlCourse.label());                     // Kursus: SQL Dasar
console.log(sqlCourse.hasOwnProperty("label"));     // false

// Kumpulkan key yang dihasilkan for...in
const keys = [];
for (const key in sqlCourse) keys.push(key);
console.log(keys.join(", "));                       // title, hours, label
Hasilnya Tergantung ke Mana label Di-assign
label di-assignhanya ke jsCoursesqlCourse sendiritidak punya labelDi chain pun tidakada: undefinedsqlCourse.label()melempar TypeErrorDi-assign keCourse.prototype.labelsqlCourse sendiritidak punya labelDitemukan diCourse.prototypeKursus: SQL Dasar
Di kedua kasus, sqlCourse sendiri tidak punya label. label hanya ditemukan lewat chain kalau di-assign ke Course.prototype.

label tetap ditemukan untuk sqlCourse, yang dibuat sebelum assignment itu, karena chain dicari pada saat nama itu dipakai. Berbeda dengan summary yang ditulis di body class, label yang ditambahkan lewat assignment juga muncul di for...in, jadi kode yang me-loop key akan ikut mendapatkannya bercampur dengan key data.

Tulis Function yang Ditambahkan ke prototype dengan function

Kalau kamu menulis Course.prototype.label = () => this.title;, arrow function memakai this dari luar class. Di editor situs ini, value-nya undefined, jadi sqlCourse.label() melempar TypeError. Pakai function.

Tambahkan tampilan durasi ke daftar judul video tanpa mengubah body Video. Video dan videos sudah dideklarasikan.

① Tambahkan durationLabel, yang bisa dipanggil oleh semua judul dan mengembalikan "N jam N menit".

② Untuk setiap judul, tampilkan "Judul: N jam N menit".

③ Tampilkan apakah judul pertama punya durationLabel sendiri.

④ Loop judul pertama dengan for...in, lalu tampilkan key yang tidak dimiliki judul itu sendiri.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Untuk method summary yang ditulis di class, apa hasil jsCourse.hasOwnProperty("summary")?

Soal 2Dengan LiveCourse extends Course, apa prototype dari LiveCourse.prototype?

Soal 3Beberapa kursus sudah dibuat. Apa yang terjadi kalau setelah itu kamu meng-assign label yang ditulis dengan function ke Course.prototype?