dartfungsiclosureMenengah4 mnt baca

Closure di Dart

Fungsi yang 'mengingat' variabel di sekitarnya: counter, enkapsulasi state.

Fungsi yang punya ingatan

Fungsi biasa lupa segalanya setelah selesai: variabel lokalnya hilang. Closure adalah fungsi yang menangkap dan mengingat variabel dari lingkup tempat ia dibuat, bahkan setelah lingkup itu selesai dieksekusi.

Analogi: closure itu seperti bekal makan siang. Bekal disiapkan di dapur (lingkup pembuat), dibawa pergi, dan masih bisa dimakan di kantor (dipanggil nanti). Isi bekal = variabel yang ditangkap.

Kenapa closure ada?

Untuk membuat fungsi stateful tanpa class: counter, cache, konfigurasi terkunci. Closure juga fondasi callback async: callback "mengingat" variabel lokal yang ada saat ia didaftarkan.

Bentuk dasar

dart
Function buatCounter() {
  int hitung = 0; // variabel lokal pembuat

  // Closure: fungsi dalam yang menangkap 'hitung'
  return () {
    hitung++;
    return hitung;
  };
}

void main() {
  var counterA = buatCounter();
  var counterB = buatCounter();

  print(counterA()); // 1
  print(counterA()); // 2 (mengingat!)
  print(counterA()); // 3
  print(counterB()); // 1 (state sendiri, tidak berbagi)
}

hitung seharusnya hilang saat buatCounter() selesai. Tapi closure menyimpannya hidup. Tiap pemanggilan buatCounter() menciptakan hitung baru yang independen.

Contoh kedua, closure untuk konfigurasi:

dart
void Function(String) buatLogger(String prefix) {
  return (String pesan) {
    var waktu = DateTime.now().toIso8601String();
    print('[$waktu][$prefix] $pesan');
  };
}

void main() {
  var logApp = buatLogger('APP');
  var logDb = buatLogger('DB');

  logApp('mulai');      // [waktu][APP] mulai
  logDb('terhubung');   // [waktu][DB] terhubung
  logApp('selesai');    // [waktu][APP] selesai
}

Tiap logger "mengingat" prefix-nya sendiri. Tanpa closure, kamu harus oper prefix di setiap pemanggilan.

Closure menangkap variabel, bukan nilai

Poin subtle yang penting:

dart
void main() {
  var fungsi = <Function>[];
  for (int i = 0; i < 3; i++) {
    fungsi.add(() => print('i = $i'));
  }
  for (var f in fungsi) {
    f();
  }
  // Output: i = 0, i = 1, i = 2 (Dart: tiap iterasi variabel baru)
}

Di Dart, variabel loop for dianggap baru tiap iterasi, jadi tiap closure menangkap i yang berbeda. (Di JavaScript lama, ini jebakan terkenal karena semua closure berbagi satu i.)

Namun hati-hati dengan variabel yang diubah setelah closure dibuat:

dart
void main() {
  String status = 'menunggu';
  var cek = () => print('Status: $status');

  cek(); // Status: menunggu
  status = 'selesai';
  cek(); // Status: selesai (mengikuti perubahan!)
}

Closure melihat nilai terkini, bukan salinan saat dibuat.

Kesalahan umum

1. Mengira closure menyalin nilai

dart
void main() {
  int x = 10;
  var baca = () => x;
  x = 99;
  print(baca()); // 99, BUKAN 10!
}

Jika butuh salinan beku, salin eksplisit ke variabel final baru di dalam pembuat.

2. Berbagi state tanpa sadar

dart
void main() {
  var counter = buatCounter();
  var alias = counter; // variabel sama, state sama!
  counter();
  print(alias()); // 2, bukan 1
}

Dua variabel menunjuk closure yang sama = satu state. Butuh state independen? Panggil pembuatnya lagi.

3. Closure dalam loop async tanpa hati-hati

dart
Future<void> main() async {
  for (int i = 0; i < 3; i++) {
    Future.delayed(Duration(milliseconds: 100 * i), () {
      print('tugas $i selesai');
    });
  }
}

Di Dart ini aman (tiap iterasi i baru), tapi di bahasa lain bisa jebakan. Pahami capture semantics bahasamu.

Kesimpulan

Closure = fungsi + variabel yang ditangkapnya. Gunakan untuk counter stateful, logger terkonfigurasi, dan callback yang butuh konteks. Ingat: yang ditangkap adalah variabel (live), bukan salinan nilai.

Catatan teknis: Di balik layar, compiler Dart mengubah variabel yang ditangkap menjadi objek heap (boxing) agar hidup lebih lama dari stack frame-nya. Ini otomatis dan efisien untuk kasus umum.

Tantangan

Penghitung mundur closure

Buat buatMundur(int dari) yang mengembalikan fungsi: tiap dipanggil, nilai berkurang 1 dan mengembalikan nilai baru. Saat mencapai 0, kembalikan 0 terus.

text
Function buatMundur(int dari) {
  // TODO
  return () => 0;
}

void main() {
  var mundur = buatMundur(3);
  print(mundur()); // 2
  print(mundur()); // 1
  print(mundur()); // 0
  print(mundur()); // 0
}