
JavaScript terus berkembang dengan cepat. Setiap tahun, TC39 — komite yang bertanggung jawab atas standar ECMAScript — merilis fitur-fitur baru yang membuat kode kita semakin bersih, aman, dan ekspresif. Masalahnya? Ekosistem JavaScript bergerak begitu cepat sehingga banyak developer melewatkan fitur-fitur berharga yang sudah tersedia secara native.
Artikel ini menjawab pertanyaan sederhana: fitur-fitur apa saja yang sudah ada di JavaScript modern, tapi mungkin belum kamu ketahui? Kita tidak perlu menunggu library baru atau framework terbaru — cukup gunakan apa yang sudah ada di bahasa itu sendiri.
Berikut adalah 16 fitur JavaScript dari ES2022 hingga ES2025 yang wajib kamu kuasai, lengkap dengan konteks, contoh penggunaan, dan tips praktis implementasinya.
📅 ES2022 — Fondasi JavaScript Modern
ES2022 membawa beberapa perubahan fundamental yang menjadi dasar dari cara kita menulis JavaScript modern hari ini. Fitur-fiturnya tidak flashy, tapi sangat praktis untuk digunakan sehari-hari.
1. ✨ Top-Level await — Tidak Perlu Wrapper Lagi
Apa masalah yang diselesaikan?
Sebelum ES2022, kamu tidak bisa menggunakan await secara langsung di level paling atas sebuah module. Kamu harus membungkusnya dalam sebuah async function hanya untuk memuat konfigurasi atau inisialisasi data. Bukan masalah besar, tapi terasa seperti boilerplate yang tidak perlu.
Cara lama (boilerplate tidak perlu):
async function init() {
const config = await fetchConfig();
startApp(config);
}
init();
Sekarang:
const config = await fetchConfig();
startApp(config);
Mengapa ini penting? Logika startup menjadi lebih bersih, lebih sedikit upacara, dan lebih mudah dibaca. Ini sangat berguna ketika kamu ingin lazy-load module atau menginisialisasi koneksi database di awal file.
2. 🔒 Private Class Fields (#) — Enkapsulasi Nyata
Apa masalah yang diselesaikan?
Secara jujur — JavaScript tidak pernah benar-benar memiliki private class fields. Kita pura-pura memilikinya dengan menggunakan konvensi aneh seperti _privateVar, yang sebenarnya tidak private sama sekali (kecuali kamu menggunakan TypeScript).
Sekarang:
class User {
#id;
#password;
constructor(id, password) {
this.#id = id;
this.#password = password;
}
getInfo() {
return `User ID: ${this.#id}`;
// #password tidak bisa diakses dari luar
}
}
const user = new User(1, "secret123");
console.log(user.getInfo()); // "User ID: 1"
console.log(user.#id); // ❌ SyntaxError!
Mengapa ini penting? Enkapsulasi nyata. Abstraksi yang lebih aman dan lebih sedikit modifikasi tidak disengaja. Ini adalah fitur yang lama ditunggu-tunggu oleh para developer yang berasal dari bahasa OOP seperti Java atau C#.
3. 🧠 Error.cause — Lacak Rantai Error dengan Mudah
Apa masalah yang diselesaikan?
Berapa kali kamu kehilangan setengah hari kerja untuk debugging karena satu error memicu error lain, tapi hubungan antara keduanya hampir tidak bisa ditelusuri? Cara lama mengharuskan kamu menimpa error atau melampirkan metadata secara manual.
Sekarang:
try {
await loadUserData();
} catch (originalError) {
throw new Error("Gagal memuat data pengguna", {
cause: originalError
});
}
// Saat di-catch di layer atas:
try {
await startApp();
} catch (err) {
console.error(err.message); // "Gagal memuat data pengguna"
console.error(err.cause.message); // Error asli dari dalam
}
Mengapa ini penting? Debugging dan logging yang lebih baik. Kamu bisa melacak seluruh rantai kegagalan tanpa harus menebak-nebak. Sangat berguna dalam arsitektur berlapis seperti microservices atau aplikasi dengan banyak layer abstraksi.
4. 🎯 Object.hasOwn() — Cek Property dengan Aman
Apa masalah yang diselesaikan?
Dulu, untuk memeriksa apakah sebuah object benar-benar memiliki sebuah property (bukan diwarisi dari prototype), kamu harus menulis monster yang membingungkan ini:
Cara lama:
Object.prototype.hasOwnProperty.call(obj, "key");
Sekarang:
Object.hasOwn(obj, "key");
// Contoh nyata:
const user = { name: "Budi", age: 25 };
console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "email")); // false
Mengapa ini penting? Sintaks yang lebih bersih, lebih mudah dibaca, dan lebih sedikit kejutan edge-case. Berbeda dengan hasOwnProperty, Object.hasOwn() juga bekerja dengan benar pada object yang dibuat dengan Object.create(null).
5. 📍 .at() — Indexing Relatif yang Elegan
Apa masalah yang diselesaikan?
Pertanyaan klasik wawancara junior: bagaimana cara mendapatkan elemen terakhir dari sebuah array? Semua developer akhirnya mempelajari cara yang sedikit jelek ini.
Cara lama:
arr[arr.length - 1];
Sekarang:
arr.at(-1); // elemen terakhir
arr.at(-2); // elemen kedua dari terakhir
arr.at(0); // elemen pertama
"hello".at(-1); // "o" - juga bekerja pada string!
Mengapa ini penting? Mungkin tidak revolusioner, tapi jelas lebih ekspresif dan mudah dibaca. Bekerja pada Array, String, dan TypedArray. Sangat berguna ketika bekerja dengan stack, queue, atau riwayat navigasi.
📅 ES2023 — Upgrade Immutability
ES2023 berfokus pada satu ide besar: hindari mutasi yang tidak disengaja. Tema immutability ini membawa dampak besar terutama untuk state management dan functional programming.
6. 🧹 toSorted() — Sort Tanpa Mutasi
Masalah: Array.sort() sangat bagus… kecuali bahwa ia memutasi array asli. Seseorang lupa akan hal itu — dan tiba-tiba separuh aplikasi kamu rusak. Yang lainnya ingat, sehingga mereka secara manual meng-clone array setiap saat.
Workaround lama:
[...arr].sort();
Sekarang:
const original = [3, 1, 4, 1, 5, 9, 2, 6];
const sorted = original.toSorted();
console.log(sorted); // [1, 1, 2, 3, 4, 5, 6, 9]
console.log(original); // [3, 1, 4, 1, 5, 9, 2, 6] — tidak berubah!
// Juga mendukung comparator function:
const sortedByName = users.toSorted((a, b) => a.name.localeCompare(b.name));
Apa yang berubah: Kamu mendapatkan salinan yang sudah di-sort tanpa menyentuh data asli.
Mengapa ini penting: Sangat besar untuk state management (React, Vue, Redux) dan functional-style code. Tidak ada lagi bug misterius karena referensi array yang sama dimodifikasi di tempat lain.
7. 🔁 toReversed() & toSpliced() — Filosofi yang Sama
Filosofi yang sama: copy, bukan mutasi.
const arr = [1, 2, 3, 4, 5];
// toReversed - balik tanpa mutasi
const reversed = arr.toReversed();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5] — aman!
// toSpliced - hapus/ganti elemen tanpa mutasi
const spliced = arr.toSpliced(2, 1, 99);
console.log(spliced); // [1, 2, 99, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5] — tetap utuh!
// with() - ganti satu elemen berdasarkan index:
const withChanged = arr.with(2, 99);
console.log(withChanged); // [1, 2, 99, 4, 5]
Mengapa ini penting: Predictability. Kamu tidak secara tidak sengaja merusak kode yang berbagi referensi array yang sama. Sangat berguna dalam context React dengan state yang immutable.
8. 🔎 findLast() / findLastIndex() — Cari Dari Belakang
Masalah: Kita punya find(), tapi bagaimana jika kamu ingin elemen terakhir yang cocok? Workaround lama tidak terlalu cantik.
Cara lama:
[...arr].reverse().find(fn); // mutasi + tidak jelas
Sekarang:
const transactions = [
{ id: 1, type: "debit", amount: 100 },
{ id: 2, type: "credit", amount: 200 },
{ id: 3, type: "debit", amount: 300 },
];
// Temukan transaksi debit terakhir
const lastDebit = transactions.findLast(t => t.type === "debit");
console.log(lastDebit); // { id: 3, type: "debit", amount: 300 }
// Temukan index transaksi debit terakhir
const lastDebitIdx = transactions.findLastIndex(t => t.type === "debit");
console.log(lastDebitIdx); // 2
Mengapa ini penting: Lebih sedikit noise, intent yang lebih jelas — kode mengatakan persis apa yang kamu maksud. Berguna untuk log, riwayat, atau daftar berurutan lainnya.
📅 ES2024 — Transformasi Data & Kontrol Async
ES2024 membawa perhatian pada dua area besar: cara kita mengelompokkan dan mentransformasi data, serta cara kita mengontrol alur asynchronous yang kompleks.
9. 🧩 Object.groupBy() — Grouping Data Jadi Mudah
Masalah: Pengelompokan array biasanya berarti menulis reducer yang terlihat lebih kompleks dari masalah itu sendiri.
Cara lama:
users.reduce((acc, user) => {
(acc[user.role] ??= []).push(user);
return acc;
}, {});
Sekarang:
const users = [
{ name: "Budi", role: "admin" },
{ name: "Siti", role: "editor" },
{ name: "Ahmad", role: "admin" },
{ name: "Dewi", role: "viewer" },
];
const byRole = Object.groupBy(users, u => u.role);
/*
{
admin: [{ name: "Budi", ... }, { name: "Ahmad", ... }],
editor: [{ name: "Siti", ... }],
viewer: [{ name: "Dewi", ... }]
}
*/
// Juga bisa dengan Map:
const byRoleMap = Map.groupBy(users, u => u.role);
Mengapa ini penting: Peningkatan readability yang sangat besar. Apa yang tadinya memerlukan fungsi helper kini menjadi satu baris kode. Berguna untuk reporting, dashboard, atau grouping data dari API.
Map.groupBy() ketika kunci pengelompokan kamu bukan string — misalnya object, number, atau Symbol. Object.groupBy() mengonversi kunci ke string.10. ⚡ Promise.withResolvers() — Async Orchestration yang Bersih
Masalah: Membuat handler resolve / reject eksternal selalu terasa canggung dan sulit dibaca.
Cara lama:
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
Sekarang:
const { promise, resolve, reject } = Promise.withResolvers();
// Contoh: resolve dari luar berdasarkan event
document.getElementById("confirmBtn").addEventListener("click", () => {
resolve("confirmed");
});
document.getElementById("cancelBtn").addEventListener("click", () => {
reject(new Error("User cancelled"));
});
try {
const result = await promise;
console.log(result); // "confirmed"
} catch (e) {
console.error(e.message); // "User cancelled"
}
Mengapa ini penting: Orkestrasi async yang lebih bersih — terutama untuk queue, event, atau alur kompleks di mana resolve/reject perlu dipanggil dari luar scope constructor.
11. 📦 Resizable ArrayBuffer — Memori yang Fleksibel
Masalah: Buffer dulu memiliki ukuran tetap, yang membuat frustrasi saat bekerja dengan streaming atau data dinamis.
// Buffer yang bisa di-resize hingga 16 bytes
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
console.log(buffer.byteLength); // 8
// Resize ke 12 bytes
buffer.resize(12);
console.log(buffer.byteLength); // 12
// Atau gunakan transferable buffers:
const transferred = buffer.transfer(4);
console.log(buffer.detached); // true
console.log(transferred.byteLength); // 4
Mengapa ini penting: Penanganan memori yang lebih fleksibel untuk skenario lanjutan seperti streaming data, WebSockets, atau WebAssembly. Sangat relevan untuk aplikasi yang bekerja dengan data biner.
📅 ES2025 — JavaScript Fungsional yang Serius
ES2025 adalah rilis yang paling ambisius dalam beberapa tahun terakhir. Fokusnya pada pemrograman fungsional, keamanan, dan performa — semuanya hadir sebagai fitur native tanpa perlu library tambahan.
12. 🧠 Iterator Helpers — Pemrosesan Data Lazy
Masalah: Method array sangat bagus — tapi mereka membuat array intermediate di setiap langkah. Terkadang itu pekerjaan yang tidak perlu, terutama dengan dataset besar.
Cara lama (membuat array ekstra):
const result = arr
.map(x => x * 2) // array baru dibuat
.filter(x => x > 5) // array baru lagi
.slice(0, 3); // array baru lagi
Sekarang (pemrosesan lazy):
const result = arr.values() // iterator, bukan array baru
.map(x => x * 2)
.filter(x => x > 5)
.take(3)
.toArray();
// Semua helper yang tersedia:
iterator.map()
iterator.filter()
iterator.take()
iterator.drop()
iterator.flatMap()
iterator.reduce()
iterator.every()
iterator.some()
iterator.find()
iterator.forEach()
iterator.toArray()
Mengapa ini penting:
- Nilai diproses langkah demi langkah (lazy evaluation)
- Lebih sedikit alokasi memori
- Performa lebih baik pada dataset besar
- Pipeline gaya fungsional yang lebih alami
Bayangkan: mindset streaming daripada “buat array lagi”.
.take()). Untuk array kecil, manfaat performanya minimal.13. 🧩 New Set Methods — Operasi Himpunan Native
Masalah: Logika himpunan yang lebih canggih selalu memerlukan helper custom atau konversi ke array yang canggung.
Cara lama:
const intersection = new Set(
[...setA].filter(x => setB.has(x))
);
Sekarang:
const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([3, 4, 5, 6, 7]);
setA.intersection(setB); // Set {3, 4, 5} — yang ada di keduanya
setA.union(setB); // Set {1,2,3,4,5,6,7} — gabungan
setA.difference(setB); // Set {1, 2} — hanya di A
setA.symmetricDifference(setB); // Set {1, 2, 6, 7} — tidak di keduanya
setA.isSubsetOf(setB); // false
setA.isSupersetOf(setB); // false
setA.isDisjointFrom(setB); // false
Mengapa ini penting: Operasi matematika/set secara langsung di bahasa. Kurang boilerplate, intent yang lebih jelas. Sangat berguna untuk permission systems, feature flags, atau filtering data dengan logika himpunan.
14. 🔐 RegExp.escape() — Keamanan Regex dari Input Pengguna
Masalah: Keamanan. Membangun regular expressions dari input pengguna bisa dengan mudah merusak pola atau bahkan menimbulkan kerentanan keamanan (ReDoS attacks).
Cara lama (mudah lupa atau salah):
const safe = userInput.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(safe);
Sekarang:
// Bayangkan user mengetik: "hello (world)"
const userInput = "hello (world)";
const regex = new RegExp(RegExp.escape(userInput));
// Menghasilkan: /hello \(world\)/ — aman!
// Contoh penggunaan nyata: fitur search/highlight
function highlightText(text, searchTerm) {
const safePattern = RegExp.escape(searchTerm);
const regex = new RegExp(`(${safePattern})`, "gi");
return text.replace(regex, "$1");
}
Mengapa ini penting: Pembuatan regex yang lebih aman tanpa menulis helper sendiri setiap saat. Mencegah kerentanan di fitur search, filter, atau highlight text.
15. ⚡ Promise.try() — Unifikasi Sync dan Async
Masalah: Terkadang kamu ingin memperlakukan kode sync dan async dengan cara yang sama — terutama ketika fungsi sync mungkin melempar error.
Sebelumnya (verbose):
// Tidak konsisten: sync error vs async rejection
try {
const result = mightThrowSync();
await doSomethingAsync(result);
} catch (e) {
// handle both
}
Sekarang:
// Semua menjadi promise-based secara otomatis
await Promise.try(() => mightThrow());
// Contoh nyata:
const result = await Promise.try(() => {
const data = JSON.parse(userInput); // sync, mungkin throw
return fetchAdditionalData(data.id); // async, mungkin reject
});
// Satu .catch() menangani keduanya!
Mengapa ini penting: Semua menjadi promise-based secara otomatis, yang menyederhanakan pipeline penanganan error. Tidak perlu lagi try-catch ganda untuk menangani sync throw dan async rejection.
16. 🧊 Float16 Support — Presisi Rendah untuk Performa Tinggi
JavaScript selalu sedikit canggung dengan angka — defaultnya adalah 64-bit floating point. Kita sudah punya Float32Array, dan sekarang JS melangkah lebih jauh.
// Float16Array: representasi 16-bit
const data = new Float16Array(1024);
// Konversi angka ke/dari float16:
const f16 = Math.fround(3.14); // sudah ada sejak lama
const f16val = Float16Array.from([1.5, 2.7, 3.14]);
// Berguna untuk WebGPU computations:
const gpuBuffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.VERTEX
});
Apa artinya ini:
- Representasi numerik yang lebih kecil (16-bit)
- Penggunaan memori lebih rendah
- Transfer data lebih cepat dalam beberapa skenario GPU/ML
Mengapa ini penting: Grafik, WebGPU, machine learning, dan workload berorientasi performa mendapat manfaat dari data yang lebih kompak. Relevan untuk AI di browser dan game berbasis web.
🧭 Gambaran Besar: Pola di Balik Semua Fitur Ini

Jika kamu melihat lebih jauh, kamu akan menemukan sebuah pola yang jelas dari evolusi JavaScript dalam beberapa tahun terakhir:
| Tema | Fitur Terkait |
|---|---|
| Lebih sedikit mutasi | toSorted(), toReversed(), toSpliced(), with() |
| Intent yang lebih jelas | .at(), findLast(), Object.hasOwn(), Object.groupBy() |
| Penanganan async lebih aman | Promise.withResolvers(), Promise.try(), Error.cause, Top-Level await |
| Pemrosesan data fungsional | Iterator Helpers, New Set Methods, Object.groupBy() |
| Keamanan & enkapsulasi | Private Class Fields #, RegExp.escape() |
JavaScript tidak lagi berubah melalui revolusi yang mencolok. Ia berevolusi melalui peningkatan kecil yang praktis yang secara diam-diam membuat kode sehari-hari lebih bersih dan lebih mudah dipikirkan.
🚀 Kapan dan Bagaimana Mulai Menggunakan?
Semua fitur ES2022–ES2024 sudah tersedia di semua browser modern dan Node.js terbaru. Fitur ES2025 sudah mulai tersedia di Chrome Canary dan Node.js nightly builds.
Cek Browser Support dengan @supports dan try/catch:
// Feature detection untuk fitur yang lebih baru
if (typeof Object.groupBy === "function") {
// Gunakan Object.groupBy
} else {
// Fallback dengan reduce
}
// Atau gunakan try/catch:
try {
const test = new Set([1,2]).intersection(new Set([2,3]));
// Pakai Set methods
} catch {
// Fallback
}
Rekomendasi Adopsi:
- Langsung pakai sekarang (semua browser modern):
Object.hasOwn(),.at(),toSorted(),toReversed(),findLast(), Top-Level await, Private Class Fields,Error.cause - Pakai dengan cek kompatibilitas:
Object.groupBy(),Promise.withResolvers(), New Set Methods - Eksperimen atau production dengan polyfill: Iterator Helpers,
RegExp.escape(),Promise.try(), Float16
💎 Key Takeaway
JavaScript modern bukan sekadar penambahan fitur — ini adalah evolusi menuju bahasa yang lebih aman, lebih ekspresif, dan lebih functional. Dengan memahami fitur-fitur ini, kamu tidak hanya menulis kode yang lebih bersih, tapi juga memanfaatkan optimasi native dari engine JavaScript yang seringkali lebih cepat dari implementasi library pihak ketiga. Mulailah dengan fitur yang sudah stabil, dan secara bertahap adopsi yang baru seiring meningkatnya browser support.
📚 Referensi dan Sumber Belajar
- Artikel Original: 16 Modern JavaScript Features That Might Blow Your Mind — Sylwia Laskowska
- Stop Installing Libraries: 10 Browser APIs That Already Solve Your Problems
- MDN Web Docs — JavaScript Reference
- ECMAScript Specification — TC39
- Can I Use — Browser Support Database
- Node.green — ECMAScript Compatibility Table untuk Node.js
- TC39 Proposals — Roadmap Fitur JavaScript di Masa Depan
Artikel ini dikembangkan dari berbagai sumber referensi JavaScript modern untuk memberikan panduan praktis bagi developer Indonesia dalam mengadopsi teknologi JavaScript terbaru. Ditulis pada Maret 2026.