JavaScript ile sayfaya yeni bir öğe eklemek için önce document.createElement() ile öğeyi oluşturur, içeriğini ve özniteliklerini ayarlar, sonra append(), prepend(), before() veya after() gibi bir metotla ağaca bağlarsınız. Bir öğeyi kaldırmak için remove(), başka bir öğeyle değiştirmek için replaceWith(), kopyalamak için cloneNode() kullanılır.

Bu yazıda modern metotlarla eski appendChild(), insertBefore(), removeChild() ve replaceChild() arasındaki farkları, var olan bir düğümü eklediğinizde neden taşındığını, DocumentFragment kullanımını ve HierarchyRequestError hatasının nedenlerini MDN belgelerine göre anlatıyoruz. Seçme metotları için querySelector ile öğe seçme yazısına, serinin tamamı için Web API öğrenme rehberi sayfasına bakabilirsiniz.

createElement ile öğe oluşturmak

createElement ile öğe oluşturmak: HTML belgesinde çağrıldığında metot adı oluşturmadan önce küçük harfe çevirir..., Tarayıcının tanımadığı bir ad verirseniz hata oluşmaz; metot...

document.createElement(localName), verilen etiket adıyla yeni bir öğe oluşturur ve döndürür. Belge bir HTML belgesiyse dönen nesne bir HTMLElement'tir; "div" için HTMLDivElement, "ul" için HTMLUListElement gibi. MDN birkaç ayrıntıyı belirtir:

  • HTML belgesinde çağrıldığında metot adı oluşturmadan önce küçük harfe çevirir; createElement("DIV") ile createElement("div") aynı sonucu verir.
  • Tarayıcının tanımadığı bir ad verirseniz hata oluşmaz; metot bir HTMLUnknownElement oluşturur. Yazım hatası yaptığınızda kod sessizce çalışmaya devam eder, bu yüzden adı dikkatle yazın.
  • Ad geçerli bir öğe adı değilse, örneğin boşluk veya > içeriyorsa, InvalidCharacterError adlı bir DOMException fırlatılır.
  • "html:a" gibi nitelikli adlar bu metotla kullanılmamalıdır.
  • İkinci parametre olan options nesnesindeki is değeri, customElements.define() ile tanımlanmış özelleştirilmiş bir yerleşik öğe oluşturmak için kullanılır; ayrıntılar Web Components yazısında.

Oluşturulan öğe henüz hiçbir yere bağlı değildir: ebeveyni yoktur ve sayfada görünmez. Bu sırada içeriğini, sınıflarını ve olay dinleyicilerini rahatça ayarlayabilirsiniz. Metin eklemek için textContent özelliğini veya document.createTextNode() metodunu kullanabilirsiniz. MDN, createTextNode() metodunun HTML karakterlerini kaçırmak için de kullanılabileceğini belirtir: verdiğiniz dize HTML olarak yorumlanmaz, düz metin olarak eklenir.

javascript
const yeniDiv = document.createElement("div");
yeniDiv.className = "uyari";
yeniDiv.append(document.createTextNode("Hi there and greetings!"));

const mevcut = document.getElementById("div1");
document.body.insertBefore(yeniDiv, mevcut);

append, prepend, before ve after: modern ekleme metotları

Element arayüzündeki dört metot, ekleme konumunu adıyla söyler. Hepsi bir veya daha fazla Node ya da dize kabul eder; dizeler eşdeğer Text düğümleri olarak eklenir. Hiçbiri değer döndürmez (undefined).

MetotNereye ekler
parent.append(...)parent öğesinin son çocuğundan sonraya, yani içine ve sona
parent.prepend(...)parent öğesinin ilk çocuğundan önceye, yani içine ve başa
el.before(...)el öğesinin ebeveyninin çocuk listesine, el öğesinin hemen önüne
el.after(...)el öğesinin ebeveyninin çocuk listesine, el öğesinin hemen arkasına

MDN'in örnekleri sonucu outerHTML ile gösterir. Aşağıdaki kodlarda yorum satırları MDN sayfalarındaki çıktılardır:

javascript
const container = document.createElement("div");
const p = document.createElement("p");
container.appendChild(p);
const span = document.createElement("span");

p.before(span, "Text");
console.log(container.outerHTML);
// "<div><span></span>Text<p></p></div>"
javascript
const container = document.createElement("div");
const p = document.createElement("p");
container.appendChild(p);
const span = document.createElement("span");

p.after(span, "Text");
console.log(container.outerHTML);
// "<div><p></p><span></span>Text</div>"
javascript
let div = document.createElement("div");
div.append("Some text");
div.prepend("Headline: ");
console.log(div.textContent); // "Headline: Some text"

before() ve after() öğenin ebeveynine ekleme yaptığı için ebeveyn yoksa, yani parentNode null ise hiçbir şey olmaz; hata da fırlatılmaz. Bu, belgeye henüz bağlanmamış bir öğenin yanına ekleme yapmaya çalıştığınızda kodun sessizce işe yaramamasının nedenidir.

append() ile appendChild() arasındaki farklar

Eski kodlarda ve MDN'in giriş rehberlerinde Node.appendChild() sık görülür. MDN, öğelerle çalışırken Element.append() metodunu tercih edebileceğinizi belirtir ve farkları şöyle sıralar:

Özellikappend()appendChild()
Tanımlandığı arayüzElement (ve Document, DocumentFragment)Node
Dize kabul eder miEvet, Text düğümüne çevrilirHayır, yalnızca Node
Birden fazla argümanEvetHayır, tek düğüm
Dönüş değeriundefinedEklenen düğüm

appendChild() eklenen düğümü döndürdüğü için yeni öğeyi oluşturduğunuz satırda hem ekleyip hem de referansını tutabilirsiniz: const p = document.body.appendChild(document.createElement("p"));. Ancak MDN bu dönüş değerinin akıcı (zincirleme) bir API gibi kullanılamayacağını özellikle uyarır. Aşağıdaki kod üç kardeş paragraf eklemez; her çağrı bir önceki çağrının döndürdüğü paragrafın içine eklediği için üç iç içe paragraf oluşur:

javascript
// This doesn't append three paragraphs:
// the three elements will be nested instead of siblings
document.body
  .appendChild(document.createElement("p"))
  .appendChild(document.createElement("p"))
  .appendChild(document.createElement("p"));

insertBefore() ve referans düğümü

parent.insertBefore(newNode, referenceNode), yeni düğümü verilen referans düğümünün önüne, ebeveynin çocuğu olarak ekler. referenceNode null ise düğüm çocuk listesinin sonuna eklenir. MDN burada önemli bir uyarı yapar: ikinci parametre isteğe bağlı değildir. Açıkça bir düğüm veya null vermeniz gerekir; parametreyi hiç vermemek ya da geçersiz bir değer geçmek farklı tarayıcı sürümlerinde farklı sonuç verebilir.

DOM'da insertAfter() adında bir metot yoktur. MDN, aynı etkiyi insertBefore() ile nextSibling'i birleştirerek elde etmeyi önerir: parentDiv.insertBefore(sp1, sp2.nextSibling). sp2 son çocuksa nextSibling null döner ve sp1 listenin sonuna, yani yine sp2'nin hemen arkasına eklenir. Modern kodda aynı işi sp2.after(sp1) tek satırla yapar.

Daha esnek bir seçenek insertAdjacentElement(position, element) metodudur. "beforebegin", "afterbegin", "beforeend" ve "afterend" konumlarından birini alır ve eklenen öğeyi döndürür. Aynı konum adlarını kullanan HTML ve metin sürümlerini innerHTML, textContent ve insertAdjacentHTML yazısında anlattık.

Var olan bir düğümü eklemek onu taşır

Var olan bir düğümü eklemek onu taşır

DOM'un en çok şaşırtan davranışlarından biri şudur: belgede zaten bulunan bir düğümü appendChild(), insertBefore(), append() veya benzeri bir metotla başka bir yere eklerseniz düğüm kopyalanmaz, bulunduğu yerden alınıp yeni yerine taşınır. Önce eski ebeveynden çıkarmanıza gerek yoktur. MDN'in ifadesiyle bir düğüm belgenin iki noktasında aynı anda bulunamaz.

Aynı öğeyi iki yerde göstermek istiyorsanız önce cloneNode() ile bir kopyasını almanız gerekir. MDN, cloneNode() ile alınan kopyaların özgün düğümle otomatik olarak eşitlenmediğini de hatırlatır: birinde yaptığınız değişiklik diğerine yansımaz. Taşıma davranışı ise listeleri yeniden sıralamak için kullanışlıdır; bir satırı listenin sonuna almak için yalnızca liste.append(satir) yazmanız yeterlidir.

replaceChildren() de bu davranıştan yararlanır. MDN'in örneğinde bir <select> listesindeki seçili <option> öğeleri, diğer listenin mevcut seçenekleriyle birlikte replaceChildren()'a verilerek tek çağrıyla öteki listeye aktarılır; seçenekler eski listeden otomatik olarak çıkar.

Öğe silmek: remove() ve removeChild()

element.remove(), öğeyi ebeveyninden kaldırır. Ebeveyn yoksa hiçbir şey olmaz. Eski yöntem olan parent.removeChild(child) ise ebeveyn üzerinden çağrılır ve kaldırılan düğümü döndürür. MDN, iki hata durumunu ayrıca belirtir: child gerçekten o ebeveynin çocuğu değilse NotFoundError, child null ise TypeError fırlatılır. Aynı düğümü ikinci kez kaldırmaya çalışmak da NotFoundError verir, çünkü ilk çağrıdan sonra artık o ebeveynin çocuğu değildir.

javascript
// Ebeveyni bilmeden güvenli kaldırma (eski yöntem)
const node = document.getElementById("child");
if (node.parentNode) {
  node.parentNode.removeChild(node);
}

// Modern karşılığı
document.getElementById("child")?.remove();

Kaldırılan düğüm hemen yok olmaz. MDN'e göre bir referansı tutulduğu sürece bellekte kalır, artık DOM'un parçası değildir ve daha sonra yeniden kullanılabilir. Hiçbir referans kalmazsa bir süre sonra bellekten otomatik olarak temizlenir. removeChild()'ın döndürdüğü düğüm, cloneNode()'dan farklı olarak üzerine eklenmiş olay dinleyicilerini korur; düğümü başka bir yere eklediğinizde dinleyiciler çalışmaya devam eder.

Öğeyi veya tüm içeriği değiştirmek

element.replaceWith(...), öğeyi ebeveyninin çocuk listesinde verilen düğümler veya dizelerle değiştirir. MDN'in örneğinde bir <div> içindeki <p>, p.replaceWith(span) çağrısından sonra bir <span>'e dönüşür ve div.outerHTML değeri "<div><span></span></div>" olur. Eski karşılığı parent.replaceChild(newChild, oldChild)'dır; MDN, yeninin eskiden önce yazıldığı bu parametre sırasının alışılmadık olduğunu ve replaceWith()'in okunmasının daha kolay olabileceğini belirtir.

element.replaceChildren(...) ise öğenin tüm çocuklarını verilen yeni çocuklarla değiştirir. Argüman vermezseniz öğe tamamen boşaltılır. Bu, MDN'in removeChild() sayfasında gösterilen döngünün kısa yoludur:

javascript
// Eski yöntem: ilk çocuk kalmayana kadar sil
while (element.firstChild) {
  element.removeChild(element.firstChild);
}

// Aynı işin tek satırlık karşılığı
element.replaceChildren();

cloneNode ile kopyalamak

node.cloneNode(deep), düğümün bir kopyasını döndürür. deep true ise düğüm tüm alt ağacı ve içindeki metinlerle birlikte kopyalanır; false verilirse veya parametre hiç verilmezse yalnızca düğümün kendisi kopyalanır. Varsayılan değer false'tur. <img> ve <input> gibi boş öğelerde deep parametresinin etkisi yoktur. Kopyanın ebeveyni yoktur; siz ekleyene kadar belgenin parçası değildir.

MDN, kopyalamada neyin taşınıp neyin taşınmadığını açıkça listeler. Tüm öznitelikler ve değerleri kopyalanır; buna onclick="..." gibi öznitelikle tanımlanmış olay işleyicileri de dahildir. Buna karşılık addEventListener() ile eklenen dinleyiciler, node.onclick = fonksiyon gibi özelliklerle atanan işleyiciler ve bir <canvas> öğesinin çizilmiş görüntüsü kopyalanmaz.

Kopyayı başka bir belgede kullanacaksanız MDN, hedef belgede document.importNode() çağırmayı önerir. Aynı öneri <template> içeriği için de geçerlidir: şablonun content özelliği ayrı bir belgeye aittir ve içinde özel öğeler varsa cloneNode() ile alınan kopyadaki özel öğeler belgeye eklenene kadar yükseltilmez. document.importNode(template.content, true) ise özel öğeleri geçerli belgenin tanımlarıyla oluşturur. Şablonun HTML tarafı için template ve slot etiketi yazısına bakabilirsiniz.

DocumentFragment: ağaç dışında hazırlık

DocumentFragment, ebeveyni olmayan küçük bir belge nesnesidir. Bir belgenin parçası gibi düğüm tutar ama etkin belge ağacının parçası değildir, bu yüzden üzerinde yaptığınız değişiklikler belgeyi etkilemez. Yaygın kullanım şöyledir: bir parça oluşturursunuz, alt ağacı onun içinde kurarsınız, sonra parçayı append(), appendChild() veya insertBefore() ile belgeye eklersiniz. Bu işlem parçanın içindeki düğümleri belgeye taşır ve geride boş bir DocumentFragment bırakır. appendChild() bu durumda eklenen düğümü değil, boşalmış parçayı döndürür.

javascript
const ul = document.querySelector("ul");
const fruits = ["Apple", "Orange", "Banana", "Melon"];
const fragment = new DocumentFragment();

for (const fruit of fruits) {
  const li = document.createElement("li");
  li.textContent = fruit;
  fragment.append(li);
}

ul.append(fragment);

Boş bir parçayı new DocumentFragment() yapıcısıyla veya document.createDocumentFragment() ile oluşturabilirsiniz. Parçanın querySelector(), getElementById(), children ve replaceChildren() gibi üyeleri de vardır.

Taşırken durumu korumak: moveBefore()

parent.moveBefore(movedNode, referenceNode), bir düğümü kaldırıp yeniden eklemeden taşır. MDN'e göre insertBefore() ile taşımada sıfırlanan bazı durumlar bu metotta korunur: CSS animasyon ve geçiş durumu, <iframe> yükleme durumu, odak gibi etkileşim durumları, tam ekran durumu, popover'ların açık ya da kapalı olması ve modal <dialog> durumu. Metot yalnızca aynı belge içinde çalışır; belgeye bağlı olmayan bir düğümü bağlı bir ebeveyne taşımaya çalışmak veya tersi HierarchyRequestError verir. İkinci argüman verilmezse TypeError fırlatılır. Bu metot için tarayıcı desteğini MDN uyumluluk tablosundan kontrol edin.

HierarchyRequestError ne zaman fırlatılır?

HierarchyRequestError ne zaman fırlatılır? Ekleme bir döngü oluşturacaksa, yani eklemeye çalıştığınız düğüm hedef düğümün..., Hedef ebeveyn Document, DocumentFragment veya Element...

Ekleme metotlarının ortak hatası HierarchyRequestError'dır ve düğüm ağacının kuralları çiğnendiğinde fırlatılır. MDN'in appendChild() sayfası olası durumları sıralar; en sık karşılaşılanlar şunlardır:

  • Ekleme bir döngü oluşturacaksa, yani eklemeye çalıştığınız düğüm hedef düğümün atasıysa. Bir öğeyi kendi çocuğunun içine ekleyemezsiniz.
  • Hedef ebeveyn Document, DocumentFragment veya Element değilse; örneğin bir metin düğümüne çocuk eklemeye çalışmak.
  • Belgeye doğrudan bir metin düğümü eklemeye çalışmak veya belgede birden fazla kök öğe oluşturacak bir ekleme yapmak.
  • Bir DocumentType düğümünü Document dışında bir yere eklemek.

Mini örnek: diziden tablo satırları oluşturmak

MDN'in DOM ağacı oluşturma rehberi bir tablo örneği verir ve bir kural önerir: öğeleri yukarıdan aşağıya oluşturun, çocukları ebeveynlere aşağıdan yukarıya ekleyin. Aynı yaklaşımı bir veri dizisine uygulayalım:

html
<table id="paketler">
  <tbody></tbody>
</table>
javascript
const paketler = [
  { ad: "Başlangıç", disk: "10 GB" },
  { ad: "Profesyonel", disk: "50 GB" },
];

const tbody = document.querySelector("#paketler tbody");
const satirlar = paketler.map((paket) => {
  const tr = document.createElement("tr");
  const adHucre = document.createElement("td");
  const diskHucre = document.createElement("td");
  adHucre.textContent = paket.ad;
  diskHucre.textContent = paket.disk;
  tr.append(adHucre, diskHucre);
  return tr;
});

tbody.replaceChildren(...satirlar);

Kod her satırı bellekte kurar, hücrelere metni textContent ile yazar ve en sonda replaceChildren() ile tablonun gövdesini tek seferde yeni satırlarla değiştirir. Aynı fonksiyonu güncel veriyle tekrar çağırdığınızda eski satırlar otomatik olarak kalkar. Veriyi metin olarak yazdığımız için kullanıcıdan gelen bir değer HTML olarak yorumlanmaz.

Sık yapılan hatalar

  • Öğeyi oluşturup eklemeyi unutmak: createElement() öğeyi yalnızca bellekte oluşturur; sayfada görünmesi için bir ekleme metodu çağırmalısınız.
  • Kopyalamak isterken taşımak: Var olan bir düğümü ikinci bir yere eklemek onu ilk yerinden kaldırır. Kopya için cloneNode(true) kullanın.
  • cloneNode ile olay dinleyicisi beklemek: addEventListener() ile eklenen dinleyiciler kopyaya geçmez; kopyaya yeniden ekleyin veya olay delegasyonu kullanın.
  • insertBefore'a ikinci argümanı vermemek: Sona eklemek için bile null geçin.
  • Kimlik yinelemek: Kopyaları aynı belgeye eklerken id değerlerini güncelleyin.
  • replaceChild parametre sırasını karıştırmak: Önce yeni, sonra eski düğüm gelir.

Sık Sorulan Sorular

append mi appendChild mi kullanmalıyım?

Öğelerle çalışırken MDN append()'i tercih edebileceğinizi belirtir: birden fazla düğüm ve dize kabul eder. Eklenen düğümün referansını dönüş değerinden almak istiyorsanız veya kodunuz Element olmayan bir düğüm üzerinde çalışıyorsa appendChild() uygundur.

Bir öğeyi silince olay dinleyicileri de silinir mi?

Öğe DOM'dan çıkar ama bir referansı tutulduğu sürece bellekte kalır. MDN, removeChild()'ın döndürdüğü düğümün olay dinleyicilerini koruduğunu belirtir; düğümü yeniden eklerseniz dinleyiciler çalışır. Hiçbir referans kalmadığında düğüm bellekten temizlenir.

innerHTML ile öğe eklemek daha kolay değil mi?

Kısa olabilir, ama innerHTML verdiğiniz dizeyi HTML olarak ayrıştırır ve kullanıcıdan gelen veriyle XSS riskine yol açar. Ayrıca öğenin tüm içeriğini değiştirdiği için var olan çocuk düğümler ve onların dinleyicileri kaybolur. Farkları innerHTML ve textContent farkı yazısında, XSS'in kendisini XSS ve CSP koruma yazısında anlattık.

Bir öğenin belgeye bağlı olup olmadığını nasıl anlarım?

parentNode değeri null ise öğe hiçbir ağaca bağlı değildir. Sayfanın gövdesinde olup olmadığını öğrenmek için MDN'in contains() örneğindeki gibi document.body.contains(node) kullanabilirsiniz; ayrıntılar DOM ağacında gezinme yazısında.

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 · öğe seçme · innerHTML ve textContent · classList ve setAttribute · DOM nedir