Menu

Prototype JavaScript: Rantai di Balik Setiap Object

Apa sebenarnya prototype di JavaScript, bagaimana prototype chain mencari property, dan bagaimana sintaks class dipetakan ke mekanisme dasar yang sama.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Setiap object punya prototype

JavaScript adalah bahasa berbasis prototype. Kedengarannya eksotis, tapi idenya sederhana: setiap object punya tautan rahasia ke object lain, yaitu prototype-nya, dan saat kamu meminta property yang tidak dimiliki object itu, JavaScript mengikuti tautan itu dan mencarinya di sana.

rabbit tidak punya property eats miliknya sendiri. JavaScript mengecek rabbit, tidak menemukannya, mengikuti tautan prototype ke animal, menemukan eats: true, lalu mengembalikannya. Untuk flies, ia menelusuri rantainya, tidak menemukan apa-apa, dan mengembalikan undefined.

Perilaku cari-lalu-telusuri inilah seluruh mekanismenya. Pewarisan, method, class, semuanya dibangun di atas ini.

Prototype chain

Rantainya tidak berhenti di satu langkah. Sebuah prototype bisa punya prototype sendiri, dan seterusnya, sampai kamu bertemu null:

Jalankan, dan kamu akan melihat rabbit, lalu Object.prototype, lalu null. Itulah kenapa rabbit.toString() berfungsi walaupun kamu tidak pernah mendefinisikan toString: ia tinggal di Object.prototype, puncak dari hampir setiap rantai.

Pencarian property menelusuri rantai ini dari bawah ke atas. Sebaliknya, assignment selalu menulis ke object itu sendiri; ia tidak pernah naik ke atas. Ketidaksimetrisan ini penting dan sering mengejutkan orang.

Constructor function dan .prototype

Sebelum class ada, cara standar membuat banyak object yang mirip adalah constructor function yang dipanggil dengan new:

Dua hal terjadi saat kamu memanggil new User("Ada"):

  1. Object baru dibuat, dengan prototype-nya diatur ke User.prototype.
  2. User dijalankan dengan this terikat ke object baru itu.

greet tidak disalin ke setiap instance. Ia tinggal satu kali di User.prototype, dan ada maupun boris menemukannya dengan menelusuri rantai mereka. Itulah kenapa baris terakhir mencetak true: keduanya benar-benar fungsi yang sama.

prototype vs __proto__

Dua nama ini menjebak semua orang. Keduanya berhubungan, tapi bukan hal yang sama.

  • User.prototype adalah property pada constructor function. Ia adalah object yang menjadi prototype bagi instance yang dibuat dengan new User(...).
  • ada.__proto__ (atau Object.getPrototypeOf(ada)) adalah tautan pada instance yang menunjuk ke atas, ke prototype-nya.

Utamakan Object.getPrototypeOf(obj) daripada obj.__proto__ di kode baru. __proto__ adalah accessor lama yang dipertahankan demi kompatibilitas; fungsi itulah API resminya.

Class adalah sugar di atas semua ini

JavaScript modern membolehkanmu menulis class, tapi di baliknya kamu tetap berurusan dengan prototype. Bandingkan kedua versinya berdampingan:

greet berakhir di User.prototype, sama seperti kalau kamu menulisnya sendiri. Kata kunci class sebagian besar memberimu sintaks yang lebih rapi, aturan yang lebih ketat (kamu wajib memakai new), dan cara yang lebih bersih untuk extends, tapi model runtime-nya identik.

Mengetahui ini penting saat kamu membaca pesan error atau men-debug this. Error tentang "User.prototype.greet" bukan nama internal yang aneh: itulah tepatnya tempat method itu tinggal.

Pewarisan hanyalah rantai yang lebih panjang

extends merangkai satu prototype ke prototype lain. Prototype milik induk menjadi prototype dari prototype milik anaknya:

Mencari rex.eat menelusuri rex → Dog.prototype → Animal.prototype, menemukan eat di sana, lalu memanggilnya dengan this tetap terikat ke rex. Hanya itulah yang dilakukan extends: ia menyiapkan rantainya untukmu.

Membuat object langsung dengan sebuah prototype

Kamu sama sekali tidak butuh constructor. Object.create(proto) membuat object baru dengan prototype yang kamu tentukan:

Tanpa class, tanpa new, tanpa constructor function. Dua object berbagi satu method lewat prototype yang sama. Inilah bentuk paling dasar dari pewarisan prototype; semua yang lain dibangun di atasnya.

hasOwnProperty: milik sendiri vs diwariskan

Karena pencarian menelusuri rantai, "foo" in obj juga mengembalikan true untuk property yang diwariskan. Saat kamu perlu membedakan property yang benar-benar dimiliki object itu, pakai Object.hasOwn (atau hasOwnProperty yang lebih lama):

name ada di instance. greet ada di prototype. in menemukan keduanya; Object.hasOwn hanya menemukan yang pertama. Ini penting setiap kali kamu melakukan perulangan dengan for...in atau melakukan serialisasi object: biasanya kamu hanya menginginkan property milik sendiri.

Jangan monkey-patch prototype bawaan

Karena Array.prototype dipakai bersama oleh setiap array di programmu, kamu bisa saja menambahkan method ke dalamnya:

// Please don't.
Array.prototype.last = function () {
    return this[this.length - 1];
};

[1, 2, 3].last(); // 3

Masalahnya bukan karena ini tidak berfungsi; ini berfungsi. Masalahnya, setiap library, setiap dependency, dan setiap versi JavaScript di masa depan kini berbagi namespace itu denganmu. Saat Array.prototype.last akhirnya hadir sebagai method resmi dengan perilaku yang sedikit berbeda, kodemu (atau kode orang lain) rusak dengan cara yang halus. Kisah Array.prototype.flatten / Array.prototype.flat adalah contoh peringatan yang paling terkenal.

Simpan helper sebagai fungsi yang berdiri sendiri:

Satu permukaan bersama lebih sedikit yang bisa bertabrakan.

Cara memikirkannya

Lepaskan semua hal lain, dan prototype bermuara pada tiga aturan:

  • Setiap object punya tautan prototype (bisa jadi null).
  • Pembacaan property menelusuri rantai itu ke atas; penulisan tidak.
  • class, new, dan extends adalah cara menyiapkan rantai itu tanpa harus mengetik Object.create sendiri.

Ingat tiga hal itu, dan perilaku this, instanceof, resolusi method, serta pewarisan akan jadi masuk akal dengan sendirinya.

Berikutnya: event loop

Prototype menutup pembahasan model object. Bab berikutnya beralih ke sesuatu yang sama sekali berbeda: bagaimana JavaScript sebenarnya menjalankan kodemu dari waktu ke waktu. Event loop adalah yang membuat timer, promise, dan async/await berperilaku seperti itu, dan ia menjadi fondasi untuk semua hal async.

Pertanyaan yang sering diajukan

Apa itu prototype di JavaScript?

Setiap object JavaScript punya tautan internal ke object lain yang disebut prototype-nya. Saat kamu mengakses property yang tidak ada di object itu sendiri, JavaScript menelusuri tautan itu, yaitu prototype chain, untuk mencarinya. Rantai itulah yang membuat method yang didefinisikan sekali bisa dipakai bersama oleh banyak instance.

Apa bedanya __proto__ dan prototype?

prototype adalah property pada constructor function (dan class). Ia adalah object yang akan menjadi prototype bagi instance yang dibuat dengan new. __proto__ (atau Object.getPrototypeOf(obj)) adalah tautan sebenarnya pada sebuah instance yang menunjuk ke prototype-nya. Jadi instance.__proto__ === Constructor.prototype.

Apakah class di JavaScript hanya syntactic sugar untuk prototype?

Sebagian besar, ya. class Foo { bar() {} } menaruh bar di Foo.prototype, persis seperti kalau kamu menulis function Foo(){} dan Foo.prototype.bar = function(){}. Class menambahkan private field, aturan yang lebih ketat, dan sintaks yang lebih enak untuk extends dan super, tapi mesin di baliknya tetap prototype.

Apakah sebaiknya aku menambah method ke prototype bawaan seperti Array.prototype?

Hampir tidak pernah. Mengubah Array.prototype atau Object.prototype memengaruhi setiap array atau object di programmu, termasuk milik library. Ini bisa bentrok dengan fitur bahasa di masa depan dan merusak loop for...in. Simpan helper buatanmu di fungsi atau module sendiri.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai