Module Pattern adalah cara membungkus kode jadi satu unit yang punya bagian privat, yang cuma bisa disentuh dari dalam, dan bagian publik, yang sengaja diserahkan ke luar. Bayangin loket ekspedisi. Pelanggan cuma bisa bicara lewat loket: "kirim paket", "cek resi". Buku catatan nomor resi ada di balik meja, dan pelanggan gak bisa mencoret angka di buku itu. Di kode, loketnya adalah object yang dikembalikan, dan buku catatannya adalah variabel yang disembunyikan di dalam function.
🧪 Tebakan cepat: Kurir Kancil membungkus counter resinya dalam function, menyimpan
let count = 0di dalamnya, dan cuma me-return{ nextResi }. Setelahresi.nextResi()dipanggil dua kali,console.log(resi.count)mencetak apa? Tebak dulu, jawabannya ada di bagian "IIFE: modul dari closure".
Masalahnya: semua file berbagi satu ruang global
Di Kurir Kancil versi lama, tiap file dimuat dengan <script> biasa. Variabel var di level paling luar file seperti itu masuk ke satu ruang global yang sama untuk semua file. Dua file yang kebetulan memakai nama count sebenarnya memegang satu variabel yang sama:
// resi.js
var count = 0
function nextResi() {
count = count + 1
return "KCL-" + String(count).padStart(4, "0")
}
// promo.js (ditulis orang lain, untuk kuota promo harian)
var count = 0
function resetPromo() {
count = 0 // maksudnya cuma mereset kuota promo
}
// main.js
console.log(nextResi()) // KCL-0001
resetPromo()
console.log(nextResi()) // KCL-0001 lagi!
resetPromo mengenolkan count milik resi juga, jadi dua paket mendapat nomor resi KCL-0001. Gak ada error, cuma dua paket yang tertukar di gudang.
IIFE: modul dari closure
Sebelum JavaScript punya import dan export, cara membuat ruang privat adalah function. Variabel yang dibuat di dalam function gak bisa disentuh dari luar function itu:
const resi = (function () {
let count = 0 // privat: cuma fungsi di dalam sini yang bisa menyentuhnya
function nextResi() {
count = count + 1
return "KCL-" + String(count).padStart(4, "0")
}
return { nextResi } // publik: hanya ini yang diserahkan ke luar
})()
console.log(resi.nextResi()) // KCL-0001
console.log(resi.nextResi()) // KCL-0002
console.log(resi.count) // undefined
(function () { ... })adalah function tanpa nama, dibungkus kurung supaya dibaca sebagai nilai.()di ujungnya langsung memanggil function itu sekali. Bentuk ini disebut IIFE (Immediately Invoked Function Expression). Versi arrow-nya sama:(() => { ... })().counthidup di dalam function itu.nextResitetap bisa memakainya walau function luarnya sudah selesai jalan. Function yang membawa variabel dari tempat dia dibuat seperti ini disebut closure.return { nextResi }menyerahkan object berisinextResisaja.countgak ikut diserahkan, jadiresi.counthasilnyaundefined. Itu jawaban tebakannya.
Bentuk "tulis semua function di dalam, lalu return object yang menyebut yang mau dibuka" punya nama sendiri: Revealing Module Pattern. Kalau promo.js dibungkus dengan cara yang sama, count miliknya jadi variabel lain, dan resetPromo() gak bisa lagi mengganggu resi.
ES module: pattern yang sudah jadi fitur bahasa
JavaScript modern punya module bawaan. Tiap file module punya ruang sendiri: variabel di level paling luar file itu privat, dan yang bisa dipakai file lain cuma yang di-export.
// resi.js
let count = 0 // gak di-export, jadi cuma file ini yang bisa menyentuhnya
export function nextResi() {
count = count + 1
return "KCL-" + String(count).padStart(4, "0")
}
// main.js
import { nextResi } from "./resi.js"
console.log(nextResi()) // KCL-0001
- File jadi module kalau dimuat dengan
<script type="module">, di-importfile module lain, atau dirakit bundler seperti Vite. promo.jsboleh punyalet countsendiri. Namanya sama, tapi tiap file punya ruang sendiri, jadi gak ada yang tertimpa.- Sebuah module dijalankan sekali saja. Semua file yang meng-
importresi.jsmemakaicountyang sama. Ini bekal untuk lesson Singleton.
Playground di halaman ini menjalankan satu script, bukan beberapa file, jadi contoh yang bisa kamu jalankan di sana memakai bentuk IIFE.
Kapan gak perlu dibungkus
- Function tanpa state.
formatRupiah(n)yang cuma menghitung dari parameternya gak punya apa-apa untuk disembunyikan. Di project modern, cukupexport function formatRupiah. - IIFE di dalam ES module. File module sudah privat. Membungkusnya lagi dengan IIFE cuma menambah satu lapisan kurung.
- Butuh banyak counter terpisah. IIFE jalan sekali dan menghasilkan satu object. Kalau tiap gudang butuh counter sendiri, tulis function biasa (
function makeCounter() { ... }) dan panggil berkali-kali.
⚠️ Jebakan yang sering ketemu
vardi<script>biasa. Variabel di level paling luar file seperti itu jadi global dan bisa ditimpa file lain yang memakai nama sama. Bungkus dengan IIFE, atau pakai ES module.- Lupa
()di ujung IIFE.const resi = (function () { ... })menyimpan function-nya, bukan hasilnya.resi.nextResi()lalu gagal denganTypeError: resi.nextResi is not a function. return { count, nextResi }.countdi object itu cuma salinan angka saatreturnjalan. SesudahnextResi()dua kali,resi.counttetap0. Kalau angkanya perlu dibaca dari luar, serahkan function:getCount() { return count }.
TL;DR: Module Pattern membungkus kode dalam scope privat dan hanya mengekspos bagian yang diperlukan. Bentuk lamanya IIFE: (function () { let count = 0; return { nextResi } })(), function yang langsung dipanggil sekali, dengan count aman di dalam closure dan resi.count bernilai undefined. Bentuk modernnya ES module: variabel di level paling luar file itu privat, yang publik cuma yang di-export, dan module dijalankan sekali. Masalah yang dihilangkan: variabel global yang saling menimpa, seperti resetPromo() yang membuat resi KCL-0001 keluar dua kali. Jangan lupa () di ujung IIFE, dan serahkan function, bukan salinan angka.