Kenapa ini penting
Data di dunia nyata jarang lengkap dan jarang tetap. Respons dari API bisa punya field yang kadang ada kadang tidak. Form pendaftaran bisa punya kolom opsional. Pengaturan aplikasi bisa bertambah seiring waktu. Kamu perlu cara untuk menambah, menghapus, dan memeriksa properti dengan aman, serta cara untuk mengunci object yang tidak boleh diubah, misalnya konfigurasi atau daftar konstanta.
Tanpa teknik ini, kamu akan sering melihat error terkenal TypeError: Cannot read properties of undefined, yang muncul saat kode mencoba membaca data.user.alamat.kota padahal alamat tidak ada. Modul ini mengajarkan cara mencegahnya dengan optional chaining dan pemeriksaan properti yang benar.
Konsep inti
Menambah, mengubah, menghapus properti
- Menambah/mengubah:
obj.kunci = nilaiatauobj["kunci dengan spasi"] = nilai. Notasi kurung siku wajib dipakai kalau nama kunci berasal dari variabel atau mengandung karakter yang tidak valid di notasi titik. - Menghapus:
delete obj.kunci. Operatordeletemengembalikantruejika berhasil (atau properti memang tidak ada). - Cara tanpa mengubah object asli (immutable): pakai rest destructuring.
const { password, ...sisa } = usermenghasilkansisatanpapassword.
Memeriksa keberadaan properti
Ada beberapa cara, dan masing-masing punya makna yang sedikit berbeda:
| Cara | Mengecek | Catatan |
|---|---|---|
obj.kunci !== undefined |
nilainya bukan undefined | salah jika properti ada tapi nilainya memang undefined |
"kunci" in obj |
ada di object atau prototype-nya | "toString" in {} bernilai true |
Object.hasOwn(obj, "kunci") |
ada sebagai properti milik sendiri | cara modern yang direkomendasikan |
obj.hasOwnProperty("kunci") |
sama seperti hasOwn |
gagal untuk object tanpa prototype |
Apa itu prototype? Setiap object punya "orang tua" tempat ia mewarisi method. Object biasa mewarisi dari Object.prototype, makanya {} punya method toString walaupun kamu tidak menulisnya. Operator in ikut mencari ke orang tua; Object.hasOwn tidak.
Mengunci object: freeze, seal, preventExtensions
| Method | Tambah properti | Hapus properti | Ubah nilai |
|---|---|---|---|
Object.preventExtensions |
tidak | ya | ya |
Object.seal |
tidak | tidak | ya |
Object.freeze |
tidak | tidak | tidak |
Analogi: seal seperti kotak bekal yang tutupnya dilem, isinya boleh diganti tapi jumlah sekatnya tetap. freeze seperti kotak bekal yang dibungkus plastik keras: tidak bisa diapa-apakan.
Di strict mode, melanggar aturan ini melempar TypeError. Di mode non-strict, pelanggaran gagal diam-diam (tidak ada error, tapi tidak terjadi apa-apa), yang lebih berbahaya karena bug-nya tidak terlihat. Penting juga: freeze itu dangkal. Object di dalam object yang dibekukan masih bisa diubah, kecuali kamu membekukannya juga secara rekursif. Cek status dengan Object.isFrozen dan Object.isSealed.
Optional chaining dan nullish coalescing
a?.bmembacaa.b, tapi kalauabernilainullatauundefined, hasilnya langsungundefinedtanpa error.a?.[kunci]untuk akses dinamis,a.method?.()untuk memanggil method yang mungkin tidak ada.??(nullish coalescing) memberi nilai cadangan hanya jika nilai kirinullatauundefined. Bedanya dengan||:||juga mengganti nilai0,"", danfalse, yang sering sebenarnya nilai valid.
Contoh kode
"use strict";
// 1) Tambah, ubah, hapus
const user = { nama: "Agus", email: "agus@mail.id", password: "rahasia" };
user.umur = 25; // tambah
user["kota asal"] = "Solo"; // kunci dengan spasi
const field = "email";
user[field] = "agus@baru.id"; // kunci dari variabel
console.log(delete user.umur); // → true
console.log(user);
// → { nama: 'Agus', email: 'agus@baru.id', password: 'rahasia', 'kota asal': 'Solo' }
// 2) Menghapus tanpa mengubah asli (rest destructuring)
const { password, ...userPublik } = user;
console.log(userPublik); // → { nama: 'Agus', email: 'agus@baru.id', 'kota asal': 'Solo' }
console.log("password" in user); // → true (asli tetap utuh)
// 3) in vs Object.hasOwn
const kosong = {};
console.log("toString" in kosong); // → true (warisan prototype)
console.log(Object.hasOwn(kosong, "toString")); // → false (bukan milik sendiri)
const data = { diskon: undefined };
console.log(data.diskon !== undefined); // → false (menipu: propertinya ada)
console.log(Object.hasOwn(data, "diskon")); // → true
// Object tanpa prototype: hasOwnProperty tidak tersedia, hasOwn tetap jalan
const bersih = Object.create(null);
bersih.a = 1;
console.log(Object.hasOwn(bersih, "a")); // → true
console.log(typeof bersih.hasOwnProperty); // → undefined
// 4) freeze dan seal
const CONFIG = Object.freeze({ api: "https://api.toko.id", retry: 3, fitur: { chat: true } });
try {
CONFIG.retry = 10;
} catch (err) {
console.log(err.constructor.name); // → TypeError
}
CONFIG.fitur.chat = false; // freeze dangkal: bagian dalam masih bisa diubah
console.log(CONFIG.fitur.chat); // → false
console.log(Object.isFrozen(CONFIG)); // → true
const kursi = Object.seal({ nomor: "A1", terisi: false });
kursi.terisi = true; // boleh, ubah nilai
try {
kursi.harga = 50000; // tidak boleh, tambah properti
} catch (err) {
console.log("Tidak bisa menambah properti pada object seal");
}
console.log(kursi); // → { nomor: 'A1', terisi: true }
// 5) Deep freeze sederhana (rekursif)
function deepFreeze(obj) {
for (const nilai of Object.values(obj)) {
if (typeof nilai === "object" && nilai !== null) deepFreeze(nilai);
}
return Object.freeze(obj);
}
const TEMA = deepFreeze({ warna: { utama: "#0057ff" } });
console.log(Object.isFrozen(TEMA.warna)); // → true
// 6) Optional chaining dan ??
const respons = { user: { nama: "Lina", alamat: null, poin: 0 } };
console.log(respons.user?.alamat?.kota); // → undefined (tanpa error)
console.log(respons.user?.alamat?.kota ?? "-"); // → -
console.log(respons.user.poin || 100); // → 100 (salah! 0 dianggap kosong)
console.log(respons.user.poin ?? 100); // → 0 (benar)
console.log(respons.user.sapa?.()); // → undefined (method tidak ada, aman)
const kunciDinamis = "nama";
console.log(respons.user?.[kunciDinamis]); // → Lina
Kesalahan umum
- Memeriksa keberadaan properti dengan
if (obj.kunci). Nilai0,"", ataufalsedianggap tidak ada. PakaiObject.hasOwn(obj, "kunci"). - Memakai
inuntuk object dari data luar."constructor" in {}bernilaitrue. Kalau kamu hanya peduli properti milik sendiri, pakaiObject.hasOwn. - Mengira
Object.freezemembekukan sampai dalam. Freeze itu dangkal; bekukan object bersarang secara rekursif kalau perlu. - Memakai
||untuk nilai default angka.stok || 10mengubah stok0menjadi10. Pakaistok ?? 10. - Menebar
?.di mana-mana. Optional chaining menyembunyikan error. Pakai hanya pada bagian yang memang boleh kosong, bukan pada data yang seharusnya selalu ada.
Latihan
- Mudah, Diberikan
const produk = { nama: "Buku", harga: 0 }. Tampilkan harga dengan default"Gratis"hanya jika harganull/undefined(hasil yang benar:0). - Sedang, Buat function
hapusKunci(obj, kunci)yang mengembalikan object baru tanpa kunci tersebut, tanpa mengubah object asli. Kunci diberikan sebagai string variabel. - Menantang, Buat function
ambil(obj, path)yang membaca nilai bersarang berdasarkan teks seperti"user.alamat.kota", mengembalikanundefined(bukan error) kalau ada bagian yang kosong. Gunakansplitdan optional chaining.
Lihat solusi
// 1) Mudah
const produk = { nama: "Buku", harga: 0 };
console.log(produk.harga ?? "Gratis"); // → 0
// 2) Sedang
function hapusKunci(obj, kunci) {
const { [kunci]: _dibuang, ...sisa } = obj; // destructuring dengan computed key
return sisa;
}
const asli = { a: 1, b: 2, c: 3 };
console.log(hapusKunci(asli, "b")); // → { a: 1, c: 3 }
console.log(asli); // → { a: 1, b: 2, c: 3 }
// 3) Menantang
function ambil(obj, path) {
let hasil = obj;
for (const bagian of path.split(".")) {
hasil = hasil?.[bagian];
}
return hasil;
}
const data = { user: { alamat: { kota: "Makassar" } } };
console.log(ambil(data, "user.alamat.kota")); // → Makassar
console.log(ambil(data, "user.profil.foto")); // → undefined
Ringkasan
- Tambah atau ubah properti dengan titik atau kurung siku; hapus dengan
deleteatau, tanpa mutasi, dengan rest destructuring. inmengecek sampai ke prototype;Object.hasOwnhanya properti milik sendiri dan menjadi pilihan modern.sealmelarang tambah/hapus,freezemelarang semua perubahan; keduanya dangkal.- Di strict mode, mengubah object beku melempar
TypeError; di non-strict gagal diam-diam. ?.mencegah error saat membaca data yang mungkin kosong, dan??memberi default tanpa mengganggu0,"", ataufalse.