kodingup.camp

Beranda/JavaScript Mastery: Object & Data

Struktur Object yang Mudah Dikembangkan

Modul 4 dari 144 menit bacaScalable Object Structures

Isi modul

    Kenapa ini penting

    Membuat satu object dengan literal {} itu mudah. Tapi bagaimana kalau kamu butuh 100 object produk, 50 object karyawan, atau ribuan object pesanan yang bentuknya sama dan punya method yang sama? Menulis ulang literal satu per satu itu melelahkan dan rawan salah ketik. Lebih parah lagi, kalau suatu hari method-nya perlu diubah, kamu harus mengubah di banyak tempat.

    Programmer menyelesaikan masalah ini dengan pola (pattern) pembuatan object: cetakan yang bisa dipakai berulang. Di modul ini kamu belajar empat pola dasar: factory function, constructor function, module pattern, dan komposisi object. Pola-pola ini adalah fondasi dari class (yang akan kamu pelajari lebih dalam nanti) dan juga dipakai luas di library JavaScript. Memahami pola ini membuatmu bisa memilih struktur yang tepat untuk aplikasi yang terus tumbuh.

    Konsep inti

    Factory function

    Factory function adalah function biasa yang mengembalikan object baru. Analogi: mesin cetak kue. Kamu masukkan bahan (parameter), keluar kue (object) dengan bentuk yang sama.

    Kelebihan: sederhana, tidak perlu new, tidak ada urusan this yang membingungkan kalau kamu memakai closure. Kekurangan: kalau method ditulis di dalam factory, setiap object mendapat salinan function sendiri, sehingga boros memori untuk object dalam jumlah sangat banyak.

    Closure untuk data privat

    Closure adalah kemampuan function "mengingat" variabel dari lingkup tempat ia dibuat, bahkan setelah function luarnya selesai dijalankan. Dengan closure, kamu bisa membuat data yang tidak bisa diakses langsung dari luar, hanya lewat method yang kamu sediakan. Ini disebut enkapsulasi.

    Constructor function dan prototype

    Constructor function adalah function yang dipanggil dengan new. Konvensinya diawali huruf kapital. Saat new Produk(...) dipanggil, JavaScript:

    1. membuat object kosong baru,
    2. menghubungkan object itu ke Produk.prototype,
    3. menjalankan function dengan this = object baru,
    4. mengembalikan object tersebut.

    Method yang ditaruh di Produk.prototype dibagi bersama oleh semua object yang dibuat, jadi hemat memori. Sintaks class yang modern sebenarnya bekerja dengan mekanisme prototype yang sama, hanya tulisannya lebih rapi.

    Module pattern

    Module pattern memakai function yang langsung dijalankan (IIFE, Immediately Invoked Function Expression) atau sekadar closure untuk membuat satu object dengan data privat dan API publik. Cocok untuk hal yang cukup ada satu, misalnya keranjang belanja aktif atau logger aplikasi. Di proyek modern, peran ini sering digantikan oleh ES module (file dengan export/import), tapi pola closure-nya tetap penting dipahami.

    Komposisi object

    Daripada membuat pohon warisan yang panjang ("Kucing turunan Mamalia turunan Hewan"), komposisi menyusun object dari potongan kemampuan kecil: bisaJalan, bisaBerenang, bisaTerbang. Bebek tinggal mengambil ketiganya, ikan mengambil berenang saja. Prinsip ini sering diringkas sebagai "favor composition over inheritance": lebih fleksibel dan tidak terkunci pada hierarki yang kaku.

    Pola Cara buat Data privat Method dibagi bersama
    Factory buatX() bisa (closure) tidak (kecuali sengaja)
    Constructor new X() tidak secara bawaan ya (prototype)
    Module IIFE/closure sekali jalan ya hanya satu instance
    Komposisi gabung beberapa factory kecil bisa tergantung potongan

    Contoh kode

    "use strict";
    
    // 1) Factory function
    function buatProduk(nama, harga) {
      return {
        nama,
        harga,
        labelHarga() {
          return `${nama}: Rp${harga.toLocaleString("id-ID")}`;
        },
      };
    }
    const p1 = buatProduk("Sepatu", 350000);
    const p2 = buatProduk("Tas", 275000);
    console.log(p1.labelHarga()); // → Sepatu: Rp350.000
    console.log(p1.labelHarga === p2.labelHarga); // → false (setiap object punya salinan method)
    
    // 2) Factory dengan data privat lewat closure
    function buatDompet(saldoAwal) {
      let saldo = saldoAwal; // tidak bisa diakses langsung dari luar
      return {
        bayar(jumlah) {
          if (jumlah > saldo) return "Saldo tidak cukup";
          saldo -= jumlah;
          return `Sisa saldo: ${saldo}`;
        },
        cekSaldo: () => saldo,
      };
    }
    const dompet = buatDompet(100000);
    console.log(dompet.bayar(30000)); // → Sisa saldo: 70000
    console.log(dompet.saldo);        // → undefined (privat)
    console.log(dompet.cekSaldo());   // → 70000
    
    // 3) Constructor function + prototype
    function Karyawan(nama, gaji) {
      this.nama = nama;
      this.gaji = gaji;
    }
    Karyawan.prototype.naikGaji = function (persen) {
      this.gaji += (this.gaji * persen) / 100;
      return this.gaji;
    };
    const k1 = new Karyawan("Tono", 5000000);
    const k2 = new Karyawan("Siti", 6000000);
    console.log(k1.naikGaji(10));             // → 5500000
    console.log(k1.naikGaji === k2.naikGaji); // → true (method dibagi lewat prototype)
    console.log(k1 instanceof Karyawan);      // → true
    
    // 4) Module pattern (IIFE)
    const keranjang = (function () {
      const items = []; // privat
      return {
        tambah(nama, harga) {
          items.push({ nama, harga });
          return items.length;
        },
        total() {
          return items.reduce((jumlah, item) => jumlah + item.harga, 0);
        },
      };
    })();
    keranjang.tambah("Kaos", 80000);
    keranjang.tambah("Topi", 45000);
    console.log(keranjang.total()); // → 125000
    console.log(keranjang.items);   // → undefined
    
    // 5) Komposisi: rakit object dari kemampuan kecil
    const bisaJalan = (state) => ({
      jalan: () => `${state.nama} berjalan`,
    });
    const bisaBerenang = (state) => ({
      berenang: () => `${state.nama} berenang`,
    });
    const bisaTerbang = (state) => ({
      terbang: () => `${state.nama} terbang`,
    });
    
    function buatBebek(nama) {
      const state = { nama };
      return { ...state, ...bisaJalan(state), ...bisaBerenang(state), ...bisaTerbang(state) };
    }
    function buatIkan(nama) {
      const state = { nama };
      return { ...state, ...bisaBerenang(state) };
    }
    const bebek = buatBebek("Donal");
    const ikan = buatIkan("Nemo");
    console.log(bebek.terbang());   // → Donal terbang
    console.log(ikan.berenang());   // → Nemo berenang
    console.log("terbang" in ikan); // → false
    

    Kesalahan umum

    1. Lupa new saat memanggil constructor function. Di strict mode this menjadi undefined dan muncul TypeError. Biasakan nama constructor berhuruf kapital supaya mudah dikenali, atau pakai factory/class.
    2. Menaruh method di dalam constructor, bukan di prototype. this.naikGaji = function () {} membuat salinan method per object. Taruh di Karyawan.prototype.
    3. Mengganti prototype dengan object baru lalu lupa constructor. Karyawan.prototype = { ... } menghapus properti constructor. Lebih aman menambah method satu per satu.
    4. Mengira data di closure ikut tersalin saat spread. Spread hanya menyalin properti, bukan variabel privat; method hasil spread tetap merujuk ke closure yang sama.
    5. Membuat hierarki warisan panjang untuk semua hal. Kalau kemampuan bisa dicampur-campur, komposisi lebih fleksibel daripada pewarisan bertingkat.

    Latihan

    1. Mudah, Buat factory buatSiswa(nama, kelas) yang mengembalikan object dengan method perkenalan() berbunyi "Saya <nama> dari kelas <kelas>".
    2. Sedang, Buat constructor Lingkaran(r) dengan method di prototype luas() yang mengembalikan luas dibulatkan dua desimal (pakai Math.PI). Buktikan method-nya dibagi bersama oleh dua object.
    3. Menantang, Buat factory buatCounter() dengan data privat nilai dan method naik(), turun(), reset(), dan lihat(). turun() tidak boleh membuat nilai di bawah 0.
    Lihat solusi
    "use strict";
    
    // 1) Mudah
    function buatSiswa(nama, kelas) {
      return {
        nama,
        kelas,
        perkenalan() {
          return `Saya ${nama} dari kelas ${kelas}`;
        },
      };
    }
    console.log(buatSiswa("Ari", "7B").perkenalan()); // → Saya Ari dari kelas 7B
    
    // 2) Sedang
    function Lingkaran(r) {
      this.r = r;
    }
    Lingkaran.prototype.luas = function () {
      return Number((Math.PI * this.r ** 2).toFixed(2));
    };
    const a = new Lingkaran(7);
    const b = new Lingkaran(1);
    console.log(a.luas(), b.luas()); // → 153.94 3.14
    console.log(a.luas === b.luas);  // → true
    
    // 3) Menantang
    function buatCounter() {
      let nilai = 0;
      return {
        naik: () => ++nilai,
        turun: () => (nilai > 0 ? --nilai : nilai),
        reset: () => (nilai = 0),
        lihat: () => nilai,
      };
    }
    const c = buatCounter();
    c.naik(); c.naik(); c.turun(); c.turun(); c.turun();
    console.log(c.lihat()); // → 0
    

    Ringkasan

    Kuis pemahaman

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