Mert Özen Her Satırda Daha İleri
Frontend JavaScript Temel — #19

JavaScript RegEx Temelleri: Düzenli İfadelerle Metin Aramak ve Doğrulamak

Mert Özen 3 Kas 2026 9 dk 5 görüntülenme
JavaScript RegEx Temelleri: Düzenli İfadelerle Metin Aramak ve Doğrulamak

Düzenli ifadelerle test, match, matchAll ve replace kullanıyor; karakter class'larını, niceleyicileri, grupları ve g, i, m bayraklarını öğrenip e-posta, telefon ve posta kodu kontrolü yazıyor, RegExp.escape'i tanıyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

Aynı kalıbı, aynı metin üzerinde iki kez deniyoruz:

const pattern = /cat/g;
console.log(pattern.test("cat"));
console.log(pattern.test("cat"));

İki satır da aynı şeyi soruyor: "bu metinde cat geçiyor mu?" Cevaplar da aynı mı olur? Okumaya devam etmeden önce bir tahmin yap. Cevap, yazının sonundaki tuzaklar bölümünde.

Düzenli İfade Nedir?

Daha önce string'ler yazısında includes ve indexOf ile bir metnin içinde başka bir metin aramıştık. Bu method'lar aradığın şeyi harfi harfine bildiğinde iş görür. Ama "beş rakamdan oluşan bir şey", "@ işareti olan ve sonunda nokta bulunan bir kelime" ya da "05 ile başlayan on bir haneli bir numara" arıyorsan tek bir metin yetmez. Bir kalıp tarif etmen gerekir.

Düzenli ifade (regular expression, kısaca RegEx) tam olarak bu: metinler için bir kalıp. Bunu bir elek gibi düşün. Metni eleğin üzerinden geçirirsin, deliklerin şekline uyan parçalar ayrılır. Kalıbı iki şekilde yazabilirsin:

const r1 = /\d+/g;                  // literal: iki bölü işareti arasında
const r2 = new RegExp("\\d+", "g"); // constructor: kalıp bir string olarak

const word = "apple";
const r3 = new RegExp(word, "i");    // kalıp bir değişkenden geliyor

Kalıbı önceden biliyorsan literal yazım hem kısa hem okunaklıdır. Kalıp kullanıcıdan ya da bir değişkenden geliyorsa RegExp constructor'ını kullanırsın. Bölü işaretinden sonraki harfler bayraklardır; g ve i'nin ne yaptığını birazdan göreceğiz.

İlk Method'lar: test, match ve replace

Bunları konsola yapıştırıp dene:

const pattern = /cat/i;
console.log(pattern.test("There is a Cat in the garden")); // true
console.log("There is a Cat in the garden".match(pattern)); // ["Cat", index: 11, ...]
console.log("Hello".match(/xyz/));                           // null

const text = "cat, dog, cat";
console.log(text.match(/cat/g));            // ["cat", "cat"]
console.log(text.replace(/cat/, "bird"));   // "bird, dog, cat"
console.log(text.replace(/cat/g, "bird"));  // "bird, dog, bird"

test en basit soruyu sorar: kalıba uyan bir parça var mı? Cevap true ya da false. Doğrulama işlerinde en çok bunu kullanacaksın.

match string'in bir method'udur ve davranışı g bayrağına göre değişir. g yoksa ilk eşleşmeyi ayrıntılarıyla birlikte bir array içinde verir: eşleşen metin, bulunduğu yer (index) ve birazdan göreceğimiz gruplar. g varsa bütün eşleşmeleri düz bir string array'i olarak döndürür. Hiç eşleşme yoksa iki durumda da null gelir, boş array değil; bunu unutmak çok yaygın bir hata.

replace eşleşen kısmı değiştirir. g olmadan sadece ilkini, g ile hepsini. Daha önce gördüğümüz replaceAll'a da bir düzenli ifade verebilirsin ama o zaman g bayrağı zorunludur; yoksa TypeError alırsın.

Kalıbın Yapı Taşları: Karakter Class'ları ve Çapalar

Kalıbın gücü, tek tek harfler yerine karakter türlerini tarif edebilmekten gelir:

"Order no: A-4821, total: 350 TL".match(/\d+/g); // ["4821", "350"]
"Phone: 0532-123-45-67".replace(/\D/g, "");       // "05321234567"

/^\w+$/.test("cafe");      // true
/^\w+$/.test("café");      // false
/^\p{L}+$/u.test("café");  // true

En sık kullanılanlar şunlar: \d bir rakam, \w bir harf, rakam ya da alt çizgi, \s bir boşluk karakteri (boşluk, tab, satır sonu). Büyük harfli hâlleri tersini anlatır: \D rakam olmayan her şey, \W ve \S da aynı mantıkla. İkinci satır bunun pratik bir kullanımı: rakam olmayan her şeyi silip telefon numarasını temizledik. Nokta (.) satır sonu dışında herhangi bir karakter demektir.

Köşeli parantezle kendi class'ını kurarsın: [aeiou] bir sesli harf, [a-z] a ile z arası bir harf, [0-9] bir rakam. Başına ^ koyarsan anlam tersine döner: [^0-9] rakam olmayan bir karakter.

Class'ların dışındaki ^ ve $ ise çapadır. Bir karakter değil, bir konum anlatırlar: ^ metnin başı, $ metnin sonu. Doğrulama yaparken kalıbı ikisinin arasına almak, "metnin bir parçası değil, tamamı bu kalıba uysun" demektir.

İngilizce dışındaki diller için önemli bir uyarı: \w sadece İngilizce harfleri tanır, é, ñ, ç ya da ü gibi karakterler onun gözünde harf değildir. Bütün dillerdeki harfleri kapsamak için ES2018 ile gelen \p{L} yazımını u bayrağıyla birlikte kullan. Kalıp içinde özel anlamı olan . * + ? ( ) [ ] { } | ^ $ \ / karakterlerinden birini düz karakter olarak arıyorsan önüne ters bölü koyarsın: \. gerçek bir nokta demektir.

Niceleyiciler: Kaç Tane?

Bir parçanın kaç kez tekrarlanacağını niceleyicilerle söylersin:

/^\d{5}$/.test("34000");       // true: tam 5 rakam
/^\d{2,4}$/.test("123");       // true: 2 ile 4 arası rakam
/^\d{3,}$/.test("12");         // false: en az 3 rakam
/https?:\/\//.test("http://a"); // true: s olsa da olmasa da

const text = '"apple" and "pear"';
text.match(/".+"/)[0];  // '"apple" and "pear"'
text.match(/".+?"/)[0]; // '"apple"'

{5} tam beş kez, {2,4} iki ile dört arası, {3,} en az üç kez demek. Üç kısayol da var: * sıfır ya da daha fazla, + bir ya da daha fazla, ? sıfır ya da bir kez. https? bu yüzden hem http hem https ile eşleşir.

Son iki satır niceleyicilerin bir huyunu gösteriyor: varsayılan olarak açgözlüdürler. .+ yakalayabildiği kadar uzun bir parçayı yakalar, bu yüzden ilk tırnaktan son tırnağa kadar her şeyi aldı. Niceleyiciden sonra bir ? koyarsan tembel olur ve kalıbı tamamlayan en kısa parçada durur.

Gruplar ve matchAll

Parantezler kalıbın bir bölümünü gruplar ve o bölümü ayrıca yakalar:

const result = "Delivery date: 2026-03-15".match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(result[0]); // "2026-03-15": eşleşmenin tamamı
console.log(result[1]); // "2026": birinci grup
console.log(result[3]); // "15": üçüncü grup

const named = "2026-03-15".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(named.groups.year, named.groups.month); // "2026" "03"

console.log("2026-03-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3.$2.$1")); // "15.03.2026"

Eşleşme array'inin sıfırıncı element'i bulunan metnin tamamı, sonraki element'ler de sırasıyla grupların yakaladıklarıdır. ES2018 ile gelen (?<name>...) yazımıyla gruplara isim verebilir, onlara groups üzerinden okunaklı bir şekilde ulaşabilirsin. replace içinde $1, $2 gibi ifadeler grupları temsil eder; son satırda bir önceki yazıda gördüğümüz ISO tarihini tek hamlede gün.ay.yıl biçimine çevirdik. Grubun içinde | kullanırsan "ya bu ya şu" dersin: (cat|dog).

Bir metindeki bütün eşleşmeleri gruplarıyla birlikte istiyorsan matchAll kullanırsın:

const post = "#javascript with #regex, wrapping up the #basics series";

for (const match of post.matchAll(/#(\w+)/g)) {
  console.log(match[1], match.index);
}
// "javascript" 0
// "regex" 17
// "basics" 41

match'e g verince grupları ve konumları kaybediyordun; matchAll her eşleşmenin bütün ayrıntısını verir. Sonucu bir array değil bir iterator olarak döndürür; daha önce gördüğümüz for...of ile gezersin ya da [...post.matchAll(pattern)] ile array'e çevirirsin. Bir şartı var: kalıpta g bayrağı olmak zorunda, yoksa TypeError fırlatır.

Bayraklar: g, i ve m

Bayraklar kalıbın nasıl çalışacağını değiştirir ve kapanış bölü işaretinden sonra yazılır:

"Cat cat CAT".match(/cat/gi);         // ["Cat", "cat", "CAT"]
"one\ntwo\ncafé".match(/^\w+$/gm);    // ["one", "two"]
"one\ntwo\ncafé".match(/^\w+$/g);     // null
/istanbul/i.test("İSTANBUL");         // false

g (global) ilk eşleşmede durmaz, hepsini arar. i büyük ve küçük harfi aynı sayar. m (multiline) ^ ve $'ın her satırın başını ve sonunu göstermesini sağlar; onsuz bütün metnin başını ve sonunu gösterirler. İkinci satırda "café"nin neden gelmediğini artık biliyorsun: é, \w için bir harf değil.

Son satırda da Türkçeye özgü bir sürpriz var: Türk alfabesindeki noktalı büyük İ eşleşmedi. i bayrağı harfleri dilden bağımsız, genel Unicode kurallarıyla karşılaştırır ve bu kurallara göre i'nin büyüğü I'dır, İ değil. Kullanıcıdan gelen Türkçe metinlerde arama yaparken bunu aklında tut. Bunların dışında u (Unicode), s (nokta satır sonuyla da eşleşsin) gibi başka bayraklar da var; ihtiyaç duydukça karşına çıkacaklar.

Pratik Örnekler: E-posta, Telefon, Posta Kodu

Şimdi öğrendiklerimizi bir forma uygulayalım. Kaydet, aç ve kutulara yazmaya başla:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Live Check</title>
  <style>
    .valid { color: green; }
    .invalid { color: firebrick; }
  </style>
</head>
<body>
  <p><input id="email" placeholder="Email"> <span id="email-status"></span></p>
  <p><input id="phone" placeholder="Mobile phone"> <span id="phone-status"></span></p>
  <p><input id="postalCode" placeholder="Postal code"> <span id="postalCode-status"></span></p>
  <script>
    const rules = {
      email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      phone: /^(\+90|0)?5\d{2}\s?\d{3}\s?\d{2}\s?\d{2}$/,
      postalCode: /^\d{5}$/
    };

    for (const field of Object.keys(rules)) {
      const box = document.getElementById(field);
      const status = document.getElementById(field + "-status");

      box.addEventListener("input", () => {
        const value = box.value.trim();
        if (value === "") {
          status.textContent = "";
          return;
        }
        const ok = rules[field].test(value);
        status.textContent = ok ? "Looks good" : "Wrong format";
        status.className = ok ? "valid" : "invalid";
      });
    }
  </script>
</body>
</html>

E-posta kalıbı şöyle okunur: başta boşluk ve @ olmayan bir ya da daha fazla karakter, sonra bir @, sonra yine boşluk ve @ olmayan karakterler, gerçek bir nokta ve en sonda yine aynı türden karakterler. Bilerek hoşgörülü tutuldu. E-posta adreslerinin resmî kuralları o kadar geniş ki onları eksiksiz kapsayan bir düzenli ifade okunmaz hâle gelir. Bir adresin gerçekten çalıştığını anlamanın tek kesin yolu oraya bir doğrulama e-postası göndermektir.

Telefon kalıbı Türkiye'deki cep numaralarını hedefliyor: isteğe bağlı +90 ya da 0, ardından 5 ile başlayan üç haneli operatör kodu ve aralarında isteğe bağlı boşluklar olan rakam grupları. 0532 123 45 67, +905321234567 ve 5321234567 geçer, 0212 123 45 67 geçmez. Posta kodu için Türkiye'de beş rakam yeterli bir kontrol. İlk iki hanenin il plaka kodu olmasını da istersen kalıbı /^(0[1-9]|[1-7]\d|8[01])\d{3}$/ şeklinde sıkılaştırabilirsin. Formlar yazısında gördüğümüz HTML pattern attribute'u da bu kalıpları kabul eder.

Son bir araç: kalıbı kullanıcının yazdığı bir metinden oluşturuyorsan, içindeki özel karakterler sorun çıkarır. ES2025 ile gelen RegExp.escape bu karakterleri senin yerine kaçışlar. Mayıs 2025'ten beri bütün güncel tarayıcılarda çalışıyor, ama eski tarayıcı sürümlerinde yok:

const text = "Version 3.5 and 345 came out on the same day.";
const searchTerm = "3.5";

console.log(text.match(new RegExp(searchTerm, "g")));                // ["3.5", "345"]
console.log(text.match(new RegExp(RegExp.escape(searchTerm), "g"))); // ["3.5"]

İlk satırda nokta "herhangi bir karakter" anlamına geldiği için 345 de eşleşti. RegExp.escape noktayı gerçek bir noktaya çevirince sadece aradığımız sonuç kaldı.

Sık Düşülen Tuzaklar

g bayrağıyla test kullanmak

Baştaki sorunun cevabı: önce true, sonra false. g bayrağı olan bir kalıp, kaldığı yeri lastIndex adında bir property'de hatırlar. İlk test "cat"i bulur ve lastIndex'i 3 yapar. İkinci test aramaya 3. karakterden başlar, metnin sonunda hiçbir şey bulamaz, false döner ve lastIndex'i sıfırlar. Üçüncü bir çağrı yine true derdi. Sadece "var mı?" diye soruyorsan g kullanma.

Çapaları unutmak

/\d{5}/.test("123456") true döner, çünkü altı rakamın içinde beş rakamlık bir parça var. Doğrulama kalıplarında ^ ve $'ı unutursan, metnin bir kısmı uyduğunda bütünü geçerli sayılır.

String içinde tek ters bölü

new RegExp("\d+") yazarsan string, ters bölüyü kalıba ulaşmadan yutar ve kalıp d+ olur. Constructor'a verdiğin string'lerde ters bölüyü iki kez yaz: "\\d+".

Okunmaz kalıplar yazmak

Düzenli ifadeler çok kısa sürede şifreli bir metne dönüşebilir. Bir kalıbı yazdıktan bir hafta sonra sen bile anlamakta zorlanıyorsan, onu daha küçük kontrollere böl ya da yanına ne yaptığını anlatan bir yorum yaz.

Küçük Bir Deneme

★☆☆ Dört haneli bir PIN kodunu kontrol eden bir kalıp yaz. "1234" geçsin, "12a4" ve "12345" geçmesin.

★★☆ Çalıştırmadan önce çıktıları tahmin et, sonra dene:

console.log("a1b22c333".match(/\d+/g));
console.log("a1b22c333".match(/\d/g).length);
console.log("2026-03-15".replace(/-/g, "/"));

★★★ Sayfaya bir paragraf ve bir arama kutusu koy. Kullanıcı yazdıkça, aranan kelimenin paragrafta kaç kez geçtiğini göster. Büyük ve küçük harf fark etmesin, kullanıcı nokta ya da parantez yazsa bile doğru çalışsın. İpucu: RegExp.escape ve gi bayrakları; eşleşme yoksa match'in null döndüğünü de unutma.

Bu yazıyla JavaScript Temel serisini tamamladık. Değişkenlerden düzenli ifadelere, dilin yapı taşlarını ve tarayıcıda çalışan ilk etkileşimli sayfaları birlikte kurduk. Orta seride bunların üzerine çıkacağız: destructuring ve spread gibi modern syntax'ı, map, filter ve reduce'u, closure ve this'i, class'ları ve en önemlisi asenkron dünyayı göreceğiz. Promise'ler, async/await ve Fetch API ile sayfan ilk kez bir sunucuyla konuşacak.