kodingup.camp

Beranda/JavaScript Mastery: Prototype, Class, Waktu, Error & Async

Rantai Hubungan Antar-Object

Modul 3 dari 154 menit bacaHierarchical Object Relationships

Isi modul

    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:

    __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:

    1. Apakah obj punya properti sendiri (own property) bernama x? Kalau ya, pakai.
    2. Kalau tidak, cari di Object.getPrototypeOf(obj).
    3. Ulangi sampai ketemu atau sampai prototype bernilai null, lalu hasilnya undefined.

    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

    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

    1. Menyamakan __proto__ dengan prototype. __proto__ ada di semua object (menunjuk orang tuanya), prototype hanya ada di function/class (cetak biru untuk anak yang dibuat dengan new).
    2. Memakai arrow function sebagai method prototype. Arrow function tidak punya this sendiri, jadi this.nama tidak menunjuk ke instance. Gunakan function biasa atau sintaks method.
    3. Menaruh array/object di prototype sebagai data. Proto.prototype.items = [] membuat semua instance berbagi array yang sama. Data per-instance taruh di konstruktor.
    4. Mengubah prototype bawaan seperti Array.prototype.sum = .... Ini bisa bentrok dengan library atau versi JavaScript mendatang. Buat function terpisah saja.
    5. Mengandalkan for...in untuk data. Ia ikut menampilkan properti warisan yang enumerable. Pakai Object.keys/Object.entries.

    Latihan

    1. Mudah, Buat object kendaraan dengan method jalan() yang mengembalikan "<merk> berjalan". Buat motor memakai Object.create(kendaraan), isi merk, lalu panggil jalan().
    2. Sedang, Buat fungsi konstruktor Siswa(nama, nilai) dengan method di prototype lulus() (true jika nilai >= 75). Buktikan dua instance berbagi method yang sama.
    3. Menantang, Tulis function cariPemilik(obj, kunci) yang mengembalikan object di rantai prototype tempat properti kunci benar-benar didefinisikan (atau null).
    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

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.