Membuat Regex di JavaScript
Ada dua cara membuat regex di JavaScript:
1. Regex Literal
const pattern = /hello/gi;
Digunakan saat pattern sudah diketahui saat menulis kode. Lebih ringkas dan umum.
2. Constructor RegExp
const pattern = new RegExp("hello", "gi");
Digunakan saat pattern dinamis (dari variabel atau input user):
function cariKata(teks, kata) {
const regex = new RegExp(kata, "gi");
return teks.match(regex);
}
cariKata("Budi dan budi", "budi"); // ["Budi", "budi"]
Perhatian: di constructor, backslash harus di-double:
/\d+/ // literal
new RegExp("\\d+") // constructor (escape backslash)
Method yang Menggunakan Regex
RegExp.prototype.test() — Cek Kecocokan
Mengembalikan true/false:
/^\d+$/.test("123"); // true
/^\d+$/.test("abc"); // false
RegExp.prototype.exec() — Detail Kecocokan
Mengembalikan array dengan detail atau null:
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex.exec("Tanggal: 2024-03-15");
console.log(result[0]); // "2024-03-15" (full match)
console.log(result[1]); // "2024" (group 1)
console.log(result.index); // 9 (posisi di string)
Dengan flag g, exec() bisa dipanggil berulang:
const regex = /\d+/g;
const teks = "Ada 3 kucing dan 12 ikan";
let match;
while ((match = regex.exec(teks)) !== null) {
console.log(`Ditemukan "${match[0]}" di posisi ${match.index}`);
}
// Ditemukan "3" di posisi 4
// Ditemukan "12" di posisi 18
String.prototype.match() — Cari Kecocokan
// Tanpa flag g — seperti exec()
"2024-03-15".match(/(\d{4})/);
// ["2024", "2024", index: 0]
// Dengan flag g — array semua kecocokan (tanpa groups)
"Ada 3 dan 12 dan 100".match(/\d+/g);
// ["3", "12", "100"]
String.prototype.matchAll() — Semua Detail
Mengembalikan iterator dengan detail lengkap untuk setiap kecocokan:
const teks = "harga: $50, diskon: $10";
const matches = [...teks.matchAll(/\$(\d+)/g)];
matches.forEach(m => {
console.log(`Full: ${m[0]}, Amount: ${m[1]}, Index: ${m.index}`);
});
// Full: $50, Amount: 50, Index: 7
// Full: $10, Amount: 10, Index: 21
String.prototype.search() — Cari Posisi
Seperti indexOf tapi menggunakan regex:
"Hello World 123".search(/\d/); // 12 (posisi digit pertama)
"Hello World".search(/\d/); // -1 (tidak ditemukan)
String.prototype.split() — Pecah String
// Pecah berdasarkan satu atau lebih spasi
"satu dua tiga".split(/\s+/);
// ["satu", "dua", "tiga"]
// Pecah berdasarkan berbagai separator
"nama:umur;kota,negara".split(/[;:,]/);
// ["nama", "umur", "kota", "negara"]
// Capture separator dalam hasil
"satu-dua_tiga".split(/([-_])/);
// ["satu", "-", "dua", "_", "tiga"]
Perhatian: lastIndex dan Flag g
Regex dengan flag g menyimpan state lastIndex. Ini bisa menyebabkan bug:
const regex = /\d/g;
console.log(regex.test("abc1")); // true
console.log(regex.lastIndex); // 4
console.log(regex.test("abc1")); // false! (mulai dari index 4)
console.log(regex.lastIndex); // 0
console.log(regex.test("abc1")); // true (reset ke 0)
Solusi: jangan reuse regex g untuk test(), atau reset lastIndex manual:
regex.lastIndex = 0;
Tips Performa
test()lebih cepat darimatch()jika hanya butuh true/false- Simpan regex di variabel jika dipakai berulang (jangan buat di dalam loop)
- Hindari pattern catastrophic backtracking seperti
(a+)+$
Regex Testing Methodology
Regex yang "kelihatan benar" sering kali bocor di edge case. Ikuti workflow ini supaya tidak ketipu pattern kamu sendiri.
1. Iterate di regex101.com
Sebelum nempel pattern ke kode, debug dulu di regex101.com atau regexr.com. Tool ini menampilkan:
- Match information — group apa yang tertangkap
- Explanation — token-by-token breakdown pattern
- Quick Reference — cheat sheet di sidebar
- Debugger — step-through engine biar paham kenapa pattern slow
Pilih flavor yang sesuai (ECMAScript untuk JS, PCRE untuk PHP, Python untuk re) — jangan asal pilih default!
2. Mulai Sederhana, Tambah Bertahap
Jangan nulis regex 80-karakter dalam satu hentakan. Bangun bertahap:
// Iterasi 1: cocokkan 4 digit
/\d{4}/
// Iterasi 2: tambah dash + 2 digit
/\d{4}-\d{2}/
// Iterasi 3: lengkapi tanggal
/\d{4}-\d{2}-\d{2}/
// Iterasi 4: anchor dan capture
/^(\d{4})-(\d{2})-(\d{2})$/
Setiap iterasi tes dulu. Kalau pecah, kamu tahu persis langkah mana yang salah.
3. Tes Match DAN Non-Match
Pemula sering hanya tes input yang seharusnya cocok. Pattern email yang lewat "[email protected]" belum tentu menolak "@@" — kedua sisi harus diuji:
const emailLike = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// Harus true (positive cases)
console.assert(emailLike.test("[email protected]"));
console.assert(emailLike.test("[email protected]"));
// Harus false (negative cases) — ini yang paling sering kelupaan!
console.assert(!emailLike.test(""));
console.assert(!emailLike.test("@gmail.com"));
console.assert(!emailLike.test("user@"));
console.assert(!emailLike.test("user gmail.com"));
console.assert(!emailLike.test("a@@b.c"));
4. Edge Case Wajib Coba
| Edge case | Kenapa berbahaya |
|---|---|
String kosong "" |
* quantifier sering bocorkan empty match |
| String sangat panjang | ReDoS lurking di (a+)+ |
| Whitespace di awal/akhir | \s vs trim — beda hasil |
Unicode (café, emoji 😀) |
\w tidak cocok karakter non-ASCII secara default |
Newline (\n) |
. tidak match newline tanpa flag s |
Karakter mirip — 0 vs O, l vs 1 |
Bug visual yang lolos |
5. Pin Flag Secara Sengaja
Jangan biarkan flag jadi "kebiasaan" — pikirkan tiap-tiap:
// JELEK: copy-paste tanpa mikir
const r = /pattern/gim;
// BAIK: tulis komentar kenapa flag itu dipakai
const r = /pattern/g; // g: butuh semua kemunculan untuk replace
Salah flag bisa diam-diam jadi bug — g di test() bikin lastIndex stateful, i di slug-validator membuat "Foo-bar" lolos padahal seharusnya tidak.
6. Unit Test Setiap Pattern
Regex yang dipakai di production layak punya test sendiri — jangan tunggu produksi yang menemukan bug-nya:
describe("phone regex", () => {
const r = /^(\+62|0)8\d{8,11}$/;
test.each([
["08123456789", true],
["+6281234567890", true],
["", false],
["abc", false],
["08", false],
["08123456789012345", false], // terlalu panjang
])("phone(%s) → %s", (input, expected) => {
expect(r.test(input)).toBe(expected);
});
});
🎭 Analogi sehari-hari: Regex literal /foo/ vs RegExp constructor new RegExp("foo") = quote langsung vs cite yang dirakit. Literal = hardcode, gak bisa diubah saat runtime. Constructor = build dari string variable (dynamic). Pattern dari user input → wajib constructor + escape!
💡 g flag stateful trap: Regex dengan g punya lastIndex. Reuse di test() = bug halus.
const r = /foo/g;
r.test("foo"); // true, lastIndex=3
r.test("foo"); // false! (start dari lastIndex=3)
Solusi: jangan reuse g regex untuk test(), atau pakai String.matchAll() (no state).
⚠️ Jebakan klasik di JS:
new RegExp(userInput)tanpa escape = ReDoS attack vector. Pakai escape function dulu- Lupa escape backslash di constructor —
new RegExp("\d")= literal\d, perlunew RegExp("\\d") match()tanpag= result punya groups, dengang= array string. Beda strukturreplace()first match only kecuali pakaig. Banyak yang ketipusplit()dengan regex capture group = include separator di hasil
🎯 Method cheat:
- Cek match:
regex.test(str)(boolean) - Detail match:
str.match(regex)atauregex.exec(str) - Iterator semua match:
str.matchAll(regex)(modern, recommended) - Posisi pertama:
str.search(regex)(index atau -1) - Replace:
str.replace(regex, replacement)atau function callback - Pecah:
str.split(regex)
TL;DR: Regex literal /.../ untuk static, new RegExp(str) untuk dynamic. g flag stateful = bug source. matchAll() modern + safe. Pattern dari user input wajib escape (regex injection). Test methodology: iterate di regex101, test match + non-match, edge case (empty, unicode, newline).