Angka 0 bukan data yang hilang — harga diskon Rp0 adalah harga yang sah. String kosong "" juga bukan data yang hilang — pengguna boleh memilih tidak mengisi nama panggilan. Sayangnya, operator || dari chapter sebelumnya tidak bisa membedakan “sengaja bernilai nol” dengan “belum diisi”: keduanya dianggap salah, sehingga nilai cadangan ikut terpakai.

Chapter ini memperkenalkan operator yang lebih teliti: nullish coalescing, ditulis ??. Operator ini hanya mengalihkan ke nilai cadangan ketika nilai aslinya benar-benar null atau undefined — dua penanda “tidak ada data” yang sudah kita pelajari di section Values dan Types.

Masalah yang Ditinggalkan || #

Ingat kembali aturan || pada chapter Logical Operators: a || b mengembalikan a bila a dianggap benar (truthy), atau mengembalikan b bila a dianggap salah (falsy). Daftar falsy mencakup 0 dan "", dan di situlah masalahnya. Untuk kasus “harga diskon Rp0”, || justru mengganti nilai sah itu dengan cadangan — padahal yang kita inginkan hanyalah mengganti data yang belum diisi. Kita butuh operator yang menanyakan “apakah nilainya null atau undefined?”, bukan “apakah nilainya dianggap salah?”.

?? Hanya Peduli null dan undefined #

Aturan a ?? b sangat sempit: kembalikan a kecuali a bernilai null atau undefined, barulah kembalikan b. Nilai lain — 0, "", false, NaN — tetap dipertahankan. Berkas berikut membandingkan keduanya secara langsung pada tiga nilai yang sama:

// nilai-cadangan.js
// Membandingkan ?? dan || pada nilai 0, string kosong, null, dan undefined.

const hargaDiskon = 0;

// ?? hanya mengalihkan ke cadangan pada null dan undefined.
console.log(hargaDiskon ?? 99999);

// || menganggap 0 falsy, lalu memakai cadangan.
console.log(hargaDiskon || 99999);

const namaPengguna = "";

// String kosong tetap dipertahankan oleh ??.
console.log(namaPengguna ?? "Anonim");

// || menganggap string kosong falsy, lalu memakai cadangan.
console.log(namaPengguna || "Anonim");

const kotaPengiriman = null;

// null dan undefined adalah satu-satunya pemicu ??.
console.log(kotaPengiriman ?? "Belum diisi");
console.log(kotaPengiriman || "Belum diisi");

Output yang Dihasilkan:

text
0
99999

Anonim
Belum diisi
Belum diisi

Mari kita telaah baris-baris pentingnya:

  1. Baris 4 (const hargaDiskon = 0;): nilai 0 dipilih sengaja, karena inilah nilai yang diperlakukan berbeda oleh ?? dan ||.
  2. Baris 7 (console.log(hargaDiskon ?? 99999);): 0 bukan null dan bukan undefined, sehingga ?? mengembalikan 0 apa adanya. Keluaran pertama adalah 0 — nilai sah tetap hidup.
  3. Baris 10 (console.log(hargaDiskon || 99999);): || menganggap 0 salah dan mengembalikan cadangan 99999. Keluaran kedua menunjukkan kelemahan || untuk kasus ini.
  4. Baris 15 (console.log(namaPengguna ?? "Anonim");): string kosong "" juga bukan nullish, sehingga ?? mengembalikannya. Keluaran ketiga tampak kosong — itu memang isi "" yang dicetak apa adanya, bukan baris yang terlewat.
  5. Baris 18 (console.log(namaPengguna || "Anonim");): || menganggap "" salah, lalu mencetak "Anonim". Untuk nama panggilan yang memang sengaja kosong, perilaku ini kurang tepat.
  6. Baris 23–24 (kotaPengiriman ?? "Belum diisi" dan kotaPengiriman || "Belum diisi"): pada null, kedua operator akhirnya sepakat. Keduanya mengembalikan "Belum diisi" — dua baris terakhir keluaran identik.

Kesimpulannya sederhana: ketika nilai 0, "", atau false harus dipertahankan sebagai data sah, gunakan ??. Ketika justru semua nilai falsy ingin diganti cadangan, || masih tepat.

Asal Nama: Nilai Nullish #

Nama operator ini bukan istilah acak. Dalam kosakata JavaScript, “nullish” merujuk pada dua nilai saja: null dan undefined — dua penanda resmi bahwa sebuah wadah belum berisi apa pun. Kita bisa membuktikan ketelitian cakupannya dengan nilai-nilai falsy lain:

js
console.log(false ?? "ganti");
console.log(NaN ?? "ganti");

Output yang Dihasilkan:

text
false
NaN

Meskipun false dan NaN dianggap salah oleh ||, keduanya bukan nullish, sehingga ?? mengembalikannya apa adanya. Dengan demikian pilihan antara ?? dan || selalu bisa dirumuskan sebagai pertanyaan: apakah hanya “belum ada data” yang ingin diganti (pakai ??), atau seluruh nilai yang dianggap salah sekaligus (pakai ||)?

Kapan Memakai ?? dan Kapan || #

SituasiPilihanAlasan
0 adalah nilai sah (diskon, stok, volume)??`
String kosong adalah nilai sah??`
Hanya null/undefined yang perlu diganti??Cakupan ?? persis seperti itu
Semua nilai falsy sekaligus ingin diganti||`

Sebagai gambaran nyata: pengaturan volume aplikasi dengan nilai 0 (bisu) harus dicek dengan volume ?? 50, bukan volume || 50 — kalau tidak, pengguna yang memilih bisu akan mendengar volume kembali ke 50.

Operator ini juga menjelaskan perilaku ??= yang kita singgung di chapter Assignment Operators: catatan ??= "Tanpa catatan" hanyalah bentuk ringkas dari catatan = catatan ?? "Tanpa catatan". Karena dasarnya adalah ??, operator assignment tersebut hanya mengisi variabel yang masih null atau undefined, dan membiarkan nilai 0 atau "" yang sudah terisi tetap utuh.

Merangkai Banyak Cadangan #

Operator ?? bisa dirangkai: engine memeriksa dari kiri dan mengembalikan nilai non-nullish pertama yang ditemukan:

js
console.log(null ?? undefined ?? "akhir");
console.log(0 ?? "cadangan");

Output yang Dihasilkan:

text
akhir
0

Baris pertama melewati null dan undefined, lalu berhenti di "akhir". Baris kedua berhenti di angka pertama, 0, dan tidak pernah sampai ke "cadangan". Pola berhenti di nilai pertama berlaku meskipun nilai itu falsy — rangkaian null ?? 0 ?? "akhir" berhenti di 0 karena 0 bukan nullish:

js
console.log(null ?? 0 ?? "akhir");

Output yang Dihasilkan:

text
0

Dengan kata lain, rangkaian ?? membaca dari kiri dan berhenti pada nilai non-nullish pertama, apa pun “pendapat” aturan truthy-falsy terhadap nilai tersebut.

Dengan ??, pembedaan “belum diisi” dan “sengaja bernilai nol” sudah tertangani. Tantangan berikutnya mirip tetapi berbeda bentuk: bagaimana jika data yang belum ada itu bukan satu nilai, melainkan cabang properti yang bertingkat? Jawabannya adalah optional chaining, topik chapter berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

nilai-cadangan.js

Unduh berkas
javascript
// nilai-cadangan.js
// Membandingkan ?? dan || pada nilai 0, string kosong, null, dan undefined.

const hargaDiskon = 0;

// ?? hanya mengalihkan ke cadangan pada null dan undefined.
console.log(hargaDiskon ?? 99999);

// || menganggap 0 falsy, lalu memakai cadangan.
console.log(hargaDiskon || 99999);

const namaPengguna = "";

// String kosong tetap dipertahankan oleh ??.
console.log(namaPengguna ?? "Anonim");

// || menganggap string kosong falsy, lalu memakai cadangan.
console.log(namaPengguna || "Anonim");

const kotaPengiriman = null;

// null dan undefined adalah satu-satunya pemicu ??.
console.log(kotaPengiriman ?? "Belum diisi");
console.log(kotaPengiriman || "Belum diisi");