JavaScript'te sayfadaki bir öğeyi seçmenin en esnek yolu document.querySelector() ve document.querySelectorAll() metotlarıdır: CSS'te yazdığınız seçicinin aynısını alır, ilk eşleşen öğeyi veya tüm eşleşmeleri döndürür. Tek bir öğeyi kimliğiyle (id) bulmak için document.getElementById(), sınıf veya etiket adına göre canlı bir koleksiyon almak için getElementsByClassName() ve getElementsByTagName() da kullanılır.
Bu yazıda bu metotların neyi döndürdüğünü, büyük ve küçük harf duyarlılığını, canlı ve statik koleksiyon farkını, :scope kullanımını, geçersiz seçicilerde fırlatılan hatayı ve CSS.escape() ile kaçış yapmayı MDN belgelerine göre anlatıyoruz. DOM ağacının kendisini henüz tanımıyorsanız önce DOM nedir yazısına, serinin tamamı için Web API öğrenme rehberi sayfasına bakabilirsiniz.
Seçme metotlarına genel bakış

MDN'in seçme ve gezinme rehberi, bir öğeyi tanımlamanın üç temel yolu olduğunu söyler: id, sınıf adı (className) ve etiket adı (tagName). Document arayüzü bu üçü için birer metot sunar. Bunlara ek olarak herhangi bir CSS seçicisini kabul eden iki sorgu metodu vardır. Aşağıdaki tablo dönüş değerlerini ve nerede çağrılabildiklerini özetler:
| Metot | Döndürdüğü | Canlı mı | Çağrılabildiği yer |
|---|---|---|---|
| getElementById(id) | tek Element veya null | tek öğe | Document, DocumentFragment |
| getElementsByClassName(names) | HTMLCollection | evet, canlı | Document, Element |
| getElementsByTagName(name) | HTMLCollection | evet, canlı | Document, Element |
| getElementsByName(name) | NodeList | evet, canlı | Document |
| querySelector(selectors) | ilk Element veya null | tek öğe | Document, DocumentFragment, Element |
| querySelectorAll(selectors) | NodeList | hayır, statik | Document, DocumentFragment, Element |
MDN'e göre sorgu metotları, getElementBy ailesinin aslında bir üst kümesidir; birinin yaptığını diğeriyle de yapabilirsiniz. Aradaki iki önemli fark, koleksiyonun canlı ya da statik olması ve seçici dizesinin geçerli CSS olmak zorunda olmasıdır. Bu iki konuyu aşağıda ayrıntılı ele alıyoruz.
getElementById: kimlikle tek öğe bulmak
getElementById(), id değeri verilen dizeyle eşleşen öğeyi döndürür, bulamazsa null döner. Kimliklerin belgede benzersiz olması gerektiği için belirli bir öğeye hızlıca ulaşmanın pratik bir yoludur. MDN birkaç ayrıntının altını çizer:
- Eşleştirme büyük ve küçük harfe duyarlıdır:
document.getElementById("Main"),<div id="main">öğesini bulmaz venulldöndürür. - Metodun adındaki büyük harf düzeni önemlidir.
getElementByID()geçerli bir metot değildir ve çalışmaz. - Aynı kimliği birden fazla öğe taşıyorsa, ki bu geçersiz bir HTML'dir, metot bulduğu ilk öğeyi döndürür.
- Metot yalnızca genel
documentnesnesinde (veDocumentFragmentüzerinde) vardır; sıradan öğelerde yoktur. Kimlikler belge genelinde benzersiz olduğu için yerel bir sürüme gerek görülmemiştir. - Belgeye eklenmemiş öğeler aranmaz.
createElement()ile oluşturupidverdiğiniz bir öğeyi, ağaca ekleyene kadar bu metotla bulamazsınız.
const element = document.createElement("div");
element.id = "test";
const el = document.getElementById("test"); // el will be null!Son madde, dinamik arayüzlerde sık karşılaşılan bir hatanın nedenidir. Öğeyi oluşturduktan sonra zaten elinizde bir değişken olarak tuttuğunuz için onu tekrar aramanıza gerek yoktur; önce append() gibi bir metotla sayfaya ekleyin, ardından değişken üzerinden çalışmaya devam edin.
getElementsByClassName ve getElementsByTagName
Bu iki metot birden fazla öğe döndürür ve sonuç bir canlı HTMLCollection'dır. Canlı olması, koleksiyonun DOM değiştikçe kendiliğinden güncellenmesi demektir. MDN'in getElementsByClassName sayfası bunu bir uyarıyla belirtir: seçilen bir öğe artık koşulu sağlamıyorsa koleksiyondan otomatik olarak çıkar ve bu durum döngülerde dikkat gerektirir.
getElementsByClassName() boşlukla ayrılmış birden fazla sınıf adı alabilir. Bu durumda yalnızca verilen sınıfların tümünü taşıyan öğeler eşleşir; öğede fazladan sınıf bulunması sorun değildir. getElementsByClassName("red test"), hem red hem test sınıfına sahip öğeleri getirir. Eşleştirme büyük ve küçük harfe duyarlıdır; MDN, yalnızca quirks kipinde ASCII harflerinde duyarsız olduğunu not eder.
getElementsByTagName() ise etiket adına göre arar ve sonuçları ağaçta göründükleri sırayla verir. Özel "*" değeri tüm öğeleri getirir. HTML belgelerinde metot, HTML öğeleriyle eşleştirirken argümanı küçük harfe çevirir; SVG gibi HTML dışı öğeler ise harf duyarlı eşleşmeye devam eder. MDN, bu küçültmenin SVG'deki büyük-küçük harf karışık öğe adlarında istenmeyen sonuç verebileceğini ve o durumda getElementsByTagNameNS() kullanılabileceğini belirtir.
Her iki metodu bir öğe üzerinde çağırdığınızda arama o öğenin torunlarıyla sınırlanır. MDN'in örneğindeki gibi document.getElementById("main").getElementsByClassName("test"), yalnızca main kimlikli öğenin içindeki test sınıflı öğeleri getirir. Koleksiyonun ilk öğesini almak için [0] kullanabilirsiniz; eşleşme yoksa sonuç undefined olur.
document.getElementsByName(), name özniteliği verilen değere eşit olan öğeleri canlı bir NodeList olarak döndürür. MDN'e göre sonuç, name özniteliğini desteklemeyen öğeler dahil, bu özniteliği taşıyan tüm öğeleri içerir. MDN'in örneğinde getElementsByName("up")[0].tagName değeri "INPUT" olur. Form alanlarına erişmenin form tarafındaki yolları için form gönderme ve FormData yazısına bakabilirsiniz.
querySelector ve querySelectorAll ile CSS seçici kullanmak

querySelector(), belgede verilen CSS seçiciyle (veya seçici grubuyla) eşleşen ilk öğeyi döndürür; eşleşme yoksa null döner. querySelectorAll() ise eşleşen tüm öğeleri statik bir NodeList içinde verir; eşleşme yoksa liste boştur. Sonuçlar belge sırasıyla gelir: ebeveynler çocuklardan, önceki kardeşler sonrakilerden önce.
// "myclass" sınıflı ilk öğe
const el = document.querySelector(".myclass");
// class="user-panel main" olan bir div içindeki name="login" input
const login = document.querySelector("div.user-panel.main input[name='login']");
// user-panel sınıfı olan ama main sınıfı olmayan div içindeki input
const login2 = document.querySelector(
"div.user-panel:not(.main) input[name='login']",
);
// note veya alert sınıflı tüm div öğeleri
const matches = document.querySelectorAll("div.note, div.alert");
// data-src özniteliği olan tüm iframe öğeleri
const frames = document.querySelectorAll("iframe[data-src]");Seçici listesinde (virgülle ayrılmış seçiciler) sıralama öncelik vermez. document.querySelector("#main, #basic, #exclamation"), kimliği bu üçünden biri olan öğelerden belgede ilk geleni döndürür. Birden fazla seçiciyle eşleşen bir öğe querySelectorAll() sonucunda yalnızca bir kez yer alır.
MDN'in rehberi, kullanıcı gizliliğini korumak için bazı sözde sınıfların sorgularda desteklenmediğini veya farklı davrandığını belirtir: :visited hiçbir eşleşme döndürmez, :link ise :any-link gibi değerlendirilir. ::before gibi sözde öğeler (pseudo-elements) DOM'da öğe olmadığı için hiçbir zaman eşleşmez; querySelectorAll()'a sözde öğe içeren bir seçici verirseniz liste her zaman boş döner. :checked ve :first-child gibi sözde sınıflar ise sorgularda kullanılabilir.
| getElementBy ile | querySelector karşılığı |
|---|---|
| document.getElementById("container") | document.querySelector("#container") |
| document.getElementsByClassName("profile big") | document.querySelectorAll(".profile.big") |
| document.getElementsByTagName("div") | document.querySelectorAll("div") |
| document.getElementsByTagName("*") | document.querySelectorAll("*") |
Geçersiz seçici: SyntaxError ve CSS.escape()
Sorgu metotlarına verdiğiniz dize geçerli bir CSS seçici olmalıdır; değilse metot SyntaxError adlı bir DOMException fırlatır. Bu kural matches() ve closest() için de geçerlidir. Sorun genellikle kimlik veya sınıf değerinden kaynaklanır: HTML belirtimi bu değerlerin geçerli bir CSS tanımlayıcısı olmasını şart koşmaz. MDN'in örneğindeki id="this?element" değeri HTML'de geçerlidir, ama ? karakteri CSS tanımlayıcılarında izinli olmadığı için #this?element seçicisi hata verir.
Çözüm, değeri seçiciye eklemeden önce CSS.escape() ile kaçırmaktır. Elle kaçış da mümkündür; bu durumda JavaScript dizesinde ters eğik çizginin kendisini de kaçırmanız gerekir:
const id = "item:42";
const item = document.querySelector(`#${CSS.escape(id)}`);
// document.getElementById(id) needs no escaping.
// elle kaçış: "?" karakterinden önce ters eğik çizgi, JS dizesinde \\ olarak yazılır
const div = document.querySelector("#this\\?element");getElementById() seçici ayrıştırmadığı için böyle bir kaçışa ihtiyaç duymaz; elinizde yalnızca bir kimlik değeri varsa bu metot hem daha basit hem de hataya daha az açıktır. MDN'in CSS.escape sayfasında gösterilen sonuçlara göre CSS.escape(".foo#bar") nokta ve diyez karakterlerinin önüne ters eğik çizgi ekler, CSS.escape("--a") ise değeri değiştirmeden bırakır.
Hatanın sayfanın geri kalanını durdurmaması için kullanıcıdan veya URL'den gelen bir değerle seçici kuruyorsanız ya kaçış uygulayın ya da çağrıyı try...catch içine alın. MDN örneğindeki gibi yakalanan hatayı ekrana yazdırmak, geliştirme sırasında sorunu hemen görmenizi sağlar. DOMException adlarının tam listesi DOMException ve güvenli bağlam hataları yazısında.
Bir öğe üzerinde sorgulamak ve :scope
Sorgu metotlarını bir öğe üzerinde çağırdığınızda sonuçlar o öğenin torunlarıyla sınırlanır, ancak MDN'in vurguladığı gibi seçici tüm belge bağlamında değerlendirilir. Bu, ilk karşılaşıldığında şaşırtıcı bir davranıştır. MDN'in örneği:
<div>
<section id="main">
<p class="note">A direct child.</p>
<div>
<p class="note">A nested paragraph.</p>
</div>
</section>
</div>const main = document.getElementById("main");
const allNotes = main.querySelectorAll("div p"); // Both paragraphs
const childNote = main.querySelectorAll(":scope div p"); // Only the second
const childNote2 = main.querySelectorAll(":scope > p"); // Only the firstdiv p seçicisi ilk paragrafı da bulur, çünkü o paragraf gerçekten bir div'in içindedir; o div arama kökünün dışında olsa bile. Seçicinin başına :scope eklemek, eşleşmeyi metodu çağırdığınız öğenin içiyle sınırlar. :scope > p ise yalnızca doğrudan çocuk olan paragrafları seçer. Bileşen tabanlı kodda bir öğenin yalnızca kendi içindeki yapıyı sorgulamak istediğinizde :scope kullanmak güvenli bir alışkanlıktır.
Canlı ve statik koleksiyon farkı
getElementsByClassName() ve getElementsByTagName() canlı bir koleksiyon döndürürken querySelectorAll() statik bir anlık görüntü döndürür. MDN'in örneği farkı açıkça gösterir:
const container = document.createElement("div");
const paragraph = document.createElement("p");
paragraph.className = "note";
container.append(paragraph);
const liveList = container.getElementsByClassName("note");
const staticList = container.querySelectorAll(".note");
console.log(liveList.length); // 1
console.log(staticList.length); // 1
paragraph.classList.remove("note");
console.log(liveList.length); // 0
console.log(staticList.length); // 1
console.log(staticList[0].className); // ""Statik liste üyeliğini korur ama düğümlerin durumunu dondurmaz: hâlâ aynı düğüm nesnesini gösterir, bu yüzden className boş dize olarak okunur. MDN, çoğu durumda istediğiniz davranışın statik liste olduğunu söyler. Canlı bir koleksiyon üzerinde dönerken üyeliği değiştirmek ise öğe atlamaya yol açar: döngüde her öğeden note sınıfını kaldırırsanız, kaldırılan öğe koleksiyondan çıkar, sonraki öğeler kayar ve döngü bir sonrakini ziyaret etmez. Çözüm, Array.from() ile bir kopya üzerinde dönmek veya doğrudan querySelectorAll() kullanmaktır.
Her iki koleksiyon türü de dizi benzeridir: length özelliği, indeksle erişim ve for...of desteği vardır. Ancak gerçek dizi olmadıkları için map() veya filter() gibi metotları yoktur. Bunlara ihtiyaç duyduğunuzda yayma sözdizimi ([...liste]) veya Array.from() ile diziye çevirin. NodeList ayrıca doğrudan forEach() sunar. İki arayüzün tüm farklarını DOM ağacında gezinme yazısında karşılaştırdık.
matches() ve closest() ile seçici testi
Bazen öğeyi bulmak değil, elinizdeki bir öğenin bir seçiciye uyup uymadığını öğrenmek istersiniz. element.matches(selectors), öğe verilen seçiciyle seçilecek olsaydı true, aksi halde false döndürür. element.closest(selectors) ise öğenin kendisinden başlayıp belge köküne doğru ebeveynleri dolaşır ve seçiciyle eşleşen ilk öğeyi döndürür; yoksa null verir. Her ikisi de geçersiz seçicide SyntaxError fırlatır.
const birds = document.querySelectorAll("li");
for (const bird of birds) {
if (bird.matches(".endangered")) {
console.log(`The ${bird.textContent} is endangered!`);
}
}closest(), olay delegasyonunda tıklanan öğenin hangi kart veya liste satırına ait olduğunu bulmak için sık kullanılır; bu deseni olay kabarcıklanması ve delegasyon yazısında, ağaçta yukarı doğru gezinmeyi ise DOM ağacında gezinme yazısında anlattık.
Mini örnek: bir listede seçili öğeleri saymak
Aşağıdaki örnek, bu yazıdaki metotları bir arada kullanır. HTML'de bir görev listesi ve bir sonuç alanı var:
<ul id="gorevler">
<li class="gorev bitti">Alan adını yönlendir</li>
<li class="gorev">SSL sertifikasını kur</li>
<li class="gorev bitti">Veritabanını yedekle</li>
</ul>
<p id="ozet"></p>const liste = document.getElementById("gorevler");
const bitenler = liste.querySelectorAll(":scope > li.gorev.bitti");
const metinler = Array.from(bitenler, (li) => li.textContent);
document.getElementById("ozet").textContent =
`${bitenler.length} görev bitti: ${metinler.join(", ")}`;Kod önce listeyi kimliğiyle bulur, ardından :scope > ile yalnızca doğrudan çocuk olan ve iki sınıfı birlikte taşıyan öğeleri seçer. querySelectorAll() statik bir liste döndürdüğü için sonradan bir öğenin sınıfı değişse bile bitenler değişmez. Array.from() listeyi metin dizisine dönüştürür ve sonuç textContent ile güvenli biçimde yazılır.
Öğe seçerken sık yapılan hatalar

- null kontrolü yapmamak:
querySelector()vegetElementById()eşleşme bulamazsanulldöner. Ardından.textContentgibi bir özelliğe erişmekTypeErrorverir. - Koleksiyonu öğe sanmak:
getElementsByClassName("test")öğenin kendisini değil, bir koleksiyon döndürür. MDN örneğinde de vurgulandığı gibi ilk öğe için[0]gerekir. - Seçicide nokta veya diyezi unutmak:
querySelector("menu")etiket adına göre arar; sınıf için".menu", kimlik için"#menu"yazmanız gerekir.getElementById()ise başında diyez olmadan yalnızca değeri ister. - Kaçış yapılmamış kimlik: Sayıyla başlayan veya özel karakter içeren bir kimliği doğrudan
#ile seçiciye koymakSyntaxError'a yol açabilir;CSS.escape()kullanın. - Canlı koleksiyonu döngüde değiştirmek: Sınıf kaldırırken veya öğe silerken canlı koleksiyon kayar; diziye çevirip öyle dönün.
- Belge hazır olmadan sorgulamak: Betik, aradığı öğeden önce çalışırsa sonuç
nullolur. Betik yükleme sırası için sayfa yaşam döngüsü yazısına bakın.
Sık Sorulan Sorular
getElementById mi querySelector mı daha iyi?
İkisi de iş görür. Elinizde yalnızca bir kimlik varsa getElementById() kaçış gerektirmediği için daha basittir. Daha karmaşık bir koşul (öznitelik, ebeveyn ilişkisi, birden fazla sınıf) gerekiyorsa querySelector() kullanın. MDN bu metotlar arasında bir hız sıralaması vermez; okunabilirliğe göre seçmek en doğrusudur.
querySelectorAll sonucu neden map() kabul etmiyor?
Çünkü NodeList gerçek bir dizi değil, dizi benzeri bir nesnedir. forEach(), entries(), keys() ve values() metotları vardır, ama map(), filter() veya some() yoktur. Array.from(liste) veya [...liste] ile diziye çevirin.
Eklediğim yeni öğeler querySelectorAll sonucunda neden görünmüyor?
querySelectorAll() çağrıldığı andaki durumu yakalayan statik bir liste döndürür. Sonradan eklenen öğeleri görmek için sorguyu yeniden çalıştırmanız ya da canlı bir koleksiyon döndüren getElementsByClassName() gibi bir metot kullanmanız gerekir.
Shadow DOM içindeki öğeleri document.querySelector bulur mu?
MDN'in rehberine göre shadow DOM ayrı bir kapsam oluşturur ve kimliklerin kendi kapsamları vardır. Shadow ağacındaki öğelere, o ağacın kökü (ShadowRoot) üzerinden sorgu yaparak ulaşırsınız. Shadow DOM'un kendisini Web Components ve shadow DOM yazısında anlattık.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · DOM nedir · DOM ağacında gezinme · DOM öğe oluşturma · olay delegasyonu