Data dari dunia nyata jarang lengkap. Formulir yang tidak semua bagiannya diisi, respons API dengan field yang hanya muncul di kondisi tertentu, dan object konfigurasi yang setiap pengisinya berbeda — semuanya menghasilkan object dengan properti yang mungkin tidak ada. Membaca properti yang hilang tidak menimbulkan error (hasilnya undefined), tetapi membaca di dalam properti yang hilang memang error, dan memperlakukan undefined sebagai data bisa menyusupkan bug ke bagian kode yang jauh dari sumbernya.
Chapter ini membahas perangkat untuk menangani ketidakpastian itu: operator in dan Object.hasOwn untuk menguji keberadaan key, optional chaining ?. untuk membaca dan memanggil dengan aman, serta pola konfigurasi opsional yang menggabungkannya dengan nilai default.
Operator in: Menguji Keberadaan Kunci #
Operator in menerima "kunci" in object dan menghasilkan true bila key tersebut ada di object — tidak peduli nilainya apa, termasuk undefined. Pembedaan ini penting: key yang ada dengan nilai undefined bukanlah key yang tidak ada:
const pekerjaan = { hasil: undefined };
console.log("hasil" in pekerjaan);
console.log(Object.hasOwn(pekerjaan, "hasil"));
console.log(pekerjaan.hasil);Output yang Dihasilkan:
true
true
undefinedTiga baris di atas membaca object yang sama dengan tiga pertanyaan berbeda. in dan Object.hasOwn menjawab soal keberadaan (true), sedangkan pembacaan langsung menjawab soal nilai (undefined). Karena itu, “properti ada tapi kosong” dan “properti tidak ada” memang dua keadaan yang bisa dibedakan — sesuatu yang tidak mungkin dilakukan hanya dengan membaca nilainya.
const setelan = { jeda: 0 };
if (setelan.jeda) {
console.log("Jeda:", setelan.jeda);
} else {
console.log("Jeda tidak terbaca");
}
console.log("jeda" in setelan);Output yang Dihasilkan:
Jeda tidak terbaca
trueif (setelan.jeda) gagal mengenali jeda bernilai 0 yang sah, sedangkan "jeda" in setelan menjawab dengan benar. Untuk pertanyaan “apakah key-nya ada”, gunakan in atau Object.hasOwn — bukan pembacaan nilai.
Object.hasOwn: Properti Milik Object Sendiri #
Object.hasOwn(object, "kunci") menghasilkan true hanya bila kunci itu milik object tersebut sendiri. Untuk object literal biasa, hasilnya sejalan dengan in. Perbedaannya muncul pada object yang mewarisi properti dari prototype-nya — materi yang dibahas tuntas di section Object-Oriented JavaScript. Untuk saat ini cukup pegang kebiasaan yang baik: ketika menguji data buatan sendiri, Object.hasOwn adalah pilihan yang paling tepat sasaran karena ia hanya melihat milik object itu.
Ada satu cara lama yang masih sering ditemui di kode lain: membandingkan hasil pembacaan dengan undefined, misalnya if (pengaturan.sms !== undefined). Bentuk ini bekerja untuk banyak kasus, tetapi jawabannya campur aduk — true bisa berarti “key-nya ada dan nilainya diisi”, dan false bisa berarti key-nya tidak ada atau key-nya ada tetapi nilainya undefined. Dua keadaan yang justru bisa kita bedakan dengan in pada cuplikan pertama tadi menjadi tidak terbedakan. Bila pembedaan itu penting, pilih in atau Object.hasOwn.
Optional Chaining: Membaca dan Memanggil dengan Aman #
Dari section Operators kita mengenal ?. untuk membaca properti di dalam jalur yang mungkin terputus. Aturannya: bila bagian di sebelum ?. bernilai null atau undefined, ekspresi berhenti di situ dan menghasilkan undefined — tanpa error. Optional chaining bekerja untuk membaca properti bertingkat (pengaturan.sms?.pengirim) dan untuk memanggil method yang mungkin tidak ada (pengaturan.kirim?.()):
const tugas = {};
console.log(tugas.selesai?.());Output yang Dihasilkan:
undefinedTanpa ?., pemanggilan tugas.selesai() pada object kosong memunculkan TypeError: tugas.selesai is not a function. Dengan ?., pemanggilan yang tidak mungkin dilakukan berubah menjadi nilai undefined yang bisa diperiksa. Dua batasan perlu diingat: optional chaining hanya untuk membaca dan memanggil — penulisan object.properti?. = nilai tidak sah dan ditolak saat penguraian kode; dan bentuk ?.() tetap menerima argumen seperti pemanggilan biasa, sehingga pengaturan.kirim?.("pagi") mengirim string itu hanya bila method kirim benar-benar ada.
Kekuatan sebenarnya muncul ketika ?. digandengkan dengan ??: membaca jalur yang mungkin terputus, lalu mengisi nilai cadangan sekaligus:
const opsi = {};
const kanal = opsi?.notifikasi?.kanal ?? "email";
console.log(kanal);
const opsiLengkap = { notifikasi: { kanal: "sms" } };
const kanal2 = opsiLengkap?.notifikasi?.kanal ?? "email";
console.log(kanal2);Output yang Dihasilkan:
email
smsSatu ekspresi yang sama menghasilkan "email" untuk object kosong dan "sms" untuk object yang isinya lengkap. Tanpa ?. dan ??, baris pertama akan error karena opsi.notifikasi tidak ada; tanpa default, pembaca tidak tahu nilai apa yang dipakai saat data kosong. Pola satu baris ini menggantikan rantai pemeriksaan panjang yang dulu ditulis bertingkat-tingkat.
Bentuk lengkapnya — kombinasi in, Object.hasOwn, ?., dan default — terlihat pada berkas contoh berikut.
Contoh Lengkap: Konfigurasi Notifikasi #
Berkas ini menguji keberadaan beberapa properti, membaca jalur yang tidak ada dengan aman, lalu menjalankan function dengan konfigurasi opsional:
// konfigurasi-notifikasi.js
// Memeriksa keberadaan properti dan membacanya dengan aman.
const pengaturan = {
kanal: "email",
email: "[email protected]",
batasHarian: 5,
kirim() {
return "Kirim " + this.kanal + " ke " + this.email;
}
};
// in: memeriksa apakah kunci ada, tidak peduli nilainya
console.log("kanal" in pengaturan);
console.log("sms" in pengaturan);
// Object.hasOwn: memeriksa properti milik object itu sendiri
console.log(Object.hasOwn(pengaturan, "batasHarian"));
console.log(Object.hasOwn(pengaturan, "sms"));
// optional chaining: membaca jalur yang mungkin tidak ada
console.log(pengaturan.sms?.pengirim);
console.log(pengaturan.kirim?.());
// nullish coalescing: nilai cadangan untuk properti yang tidak ada
const prioritas = pengaturan.prioritas ?? "normal";
console.log("Prioritas:", prioritas);
// pola konfigurasi opsional di parameter function
function kirimPesan(penerima, opsi) {
const { kanal = "email", tanda = false } = opsi || {};
const label = tanda ? " [penting]" : "";
return "Kirim " + kanal + " ke " + penerima + label;
}
console.log(kirimPesan("[email protected]", { kanal: "sms", tanda: true }));
console.log(kirimPesan("[email protected]"));Output yang Dihasilkan:
true
false
true
false
undefined
Kirim email ke [email protected]
Prioritas: normal
Kirim sms ke [email protected] [penting]
Kirim email ke [email protected]Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 15–16 (
console.log("kanal" in pengaturan);dan seterusnya):kanalada sehingga menghasilkantrue;smstidak pernah dibuat sehinggafalse.inmenjawab keberadaan, bukan nilai. - Baris 19–20 (
console.log(Object.hasOwn(...))): hasilnya sama denganinuntuk object literal ini —batasHarianmilik sendiri (true),smstidak ada (false). - Baris 23–24 (
console.log(pengaturan.sms?.pengirim);danconsole.log(pengaturan.kirim?.());): baris pertama berhenti dismsyangundefineddan menghasilkanundefinedtanpa error. Baris kedua memanggil methodkirimyang memang ada, sehingga keluarannya adalah nilai kembalian method tersebut. - Baris 27–28 (
const prioritas = pengaturan.prioritas ?? "normal";):prioritastidak ada di object, hasil pembacaannyaundefined, dan??menggantikannya dengan"normal". Bila kelak properti ini ditambahkan, nilai aslinya yang terpakai. - Baris 31–35 (
function kirimPesan(penerima, opsi) { ... }): pola konfigurasi opsional dalam bentuk utuhnya.opsi || {}menjaga function tetap aman ketika dipanggil tanpa argumen kedua; destructuring dengan default dari chapter Object Destructuring menyediakan nilai cadangan per properti. - Baris 37–38 (
console.log(kirimPesan(...))): pemanggilan pertama mengirim konfigurasi lengkap — kanalsmsdan tanda[penting]dipakai apa adanya. Pemanggilan kedua tidak mengirimopsisama sekali, sehingga defaultemaildan tanpa tanda yang bekerja.
Satu detail di baris 33 layak diperhatikan: mengapa opsi || {} dan bukan opsi ?? {}? Di konteks ini keduanya menghasilkan perilaku yang sama — null dan undefined sama-sama diganti object kosong, sedangkan object biasa (yang selalu truthy) dibiarkan lewat. Perbedaannya hanya presisi: ?? hanya merespons null/undefined, sehingga lebih aman bila argumen opsinya bisa berupa nilai falsy yang sah. Untuk argumen yang memang berupa object atau tidak ada, keduanya tepat.
Kini kita tahu cara membaca object dengan aman. Chapter berikutnya menjawab pertanyaan yang sudah beberapa kali disinggung: apa sebenarnya yang terjadi saat object “disalin”, dan bagaimana membandingkan dua object dengan benar.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
konfigurasi-notifikasi.js
Unduh berkas// konfigurasi-notifikasi.js
// Memeriksa keberadaan properti dan membacanya dengan aman.
const pengaturan = {
kanal: "email",
email: "[email protected]",
batasHarian: 5,
kirim() {
return "Kirim " + this.kanal + " ke " + this.email;
}
};
// in: memeriksa apakah kunci ada, tidak peduli nilainya
console.log("kanal" in pengaturan);
console.log("sms" in pengaturan);
// Object.hasOwn: memeriksa properti milik object itu sendiri
console.log(Object.hasOwn(pengaturan, "batasHarian"));
console.log(Object.hasOwn(pengaturan, "sms"));
// optional chaining: membaca jalur yang mungkin tidak ada
console.log(pengaturan.sms?.pengirim);
console.log(pengaturan.kirim?.());
// nullish coalescing: nilai cadangan untuk properti yang tidak ada
const prioritas = pengaturan.prioritas ?? "normal";
console.log("Prioritas:", prioritas);
// pola konfigurasi opsional di parameter function
function kirimPesan(penerima, opsi) {
const { kanal = "email", tanda = false } = opsi || {};
const label = tanda ? " [penting]" : "";
return "Kirim " + kanal + " ke " + penerima + label;
}
console.log(kirimPesan("[email protected]", { kanal: "sms", tanda: true }));
console.log(kirimPesan("[email protected]"));