跟着 MDN 学 HTML day_29:(动态构建与更新 DOM 树)
引言在掌握了 DOM 树的结构和节点导航之后下一步就是学习如何动态地创建、修改和删除 DOM 节点。DOM Level 1 核心规范提供了一系列基础方法使得开发者可以通过 JavaScript 在运行时构建完整的页面结构。本文将通过实际示例系统地介绍createElement、createTextNode、appendChild、removeChild等核心方法的使用方式以及动态操作 DOM 时应遵循的最佳实践。一、动态创建 HTML 表格动态创建 DOM 结构需要遵循自顶向下创建、自底向上挂载的原则。下面的示例展示了如何通过按钮点击事件动态生成一个完整的表格并添加到页面中。buttonidgenerate-btn生成表格/buttondividtable-container/divscriptfunctiongenerateTable(){// 第一步创建最外层的 table 元素consttbldocument.createElement(table);// 第二步创建 tbody 元素作为 table 的子节点consttblBodydocument.createElement(tbody);// 第三步循环创建行和单元格for(leti0;i3;i){// 创建 tr 元素作为 tbody 的子节点constrowdocument.createElement(tr);for(letj0;j3;j){// 创建 td 元素作为 tr 的子节点constcelldocument.createElement(td);// 创建文本节点作为 td 的子节点constcellTextdocument.createTextNode(第${i}行第${j}列);// 自底向上挂载先将文本节点挂载到 tdcell.appendChild(cellText);// 再将 td 挂载到 trrow.appendChild(cell);}// 将 tr 挂载到 tbodytblBody.appendChild(row);}// 将 tbody 挂载到 tabletbl.appendChild(tblBody);// 设置表格边框属性tbl.setAttribute(border,1);// 最后将 table 挂载到页面中的容器document.getElementById(table-container).appendChild(tbl);}document.getElementById(generate-btn).addEventListener(click,generateTable);/script创建 DOM 结构的关键思路是先在内存中从外到内依次创建所有元素节点和文本节点然后从内到外依次将子节点挂载到父节点上。如果打乱这个顺序可能会导致节点关系混乱或遗漏。二、查询现有元素并修改样式除了创建新元素DOM API 也提供了多种查询现有元素的方法。getElementsByTagName 可以在指定元素或整个文档中根据标签名查找后代元素返回一个动态的 HTMLCollection。inputtypebuttonvalue设置段落背景色idstyle-btn/p第一段文字/pp第二段文字/pp第三段文字/pscriptfunctionsetBackground(){// 获取文档中所有的 p 元素constparagraphsdocument.getElementsByTagName(p);// 通过索引访问特定段落constsecondParagraphparagraphs[1];// 直接修改 style 属性设置背景色secondParagraph.style.backgroundlightblue;secondParagraph.style.padding10px;secondParagraph.style.borderRadius4px;}document.getElementById(style-btn).addEventListener(click,setBackground);/scriptgetElementsByTagName 返回的是动态集合这意味着如果文档中的段落元素发生变化集合会自动更新。这一特性在需要实时反映 DOM 状态时很有用但在遍历集合的同时修改 DOM 结构时需要格外小心避免出现意外的跳过或死循环。三、创建文本节点与 appendChild 的使用细节createTextNode 方法用于创建一个纯文本节点该节点的 nodeType 为 TEXT_NODE。创建后必须通过 appendChild 将其挂载到某个元素节点上才能在页面中显示。pidgreeting你好/pbuttonidappend-btn添加文本/buttonbuttonidreset-btn重置/buttonscriptconstgreetingdocument.getElementById(greeting);constappendBtndocument.getElementById(append-btn);constresetBtndocument.getElementById(reset-btn);// 保存初始文本节点的引用方便后续操作letworldTextNodenull;appendBtn.addEventListener(click,(){// 创建新的文本节点worldTextNodedocument.createTextNode( 世界);// 将文本节点追加为 p 元素的最后一个子节点greeting.appendChild(worldTextNode);// 此时页面显示 你好 世界// DOM 结构中有两个文本节点你好 和 世界});resetBtn.addEventListener(click,(){if(worldTextNodeworldTextNode.parentNodegreeting){// removeChild 用于移除子节点greeting.removeChild(worldTextNode);// 页面恢复为 你好}});/scriptappendChild 总是将新节点添加为父节点的最后一个子节点。如果需要在特定位置插入节点应该使用 insertBefore 方法。文本节点虽然看起来和周围的文本连成一片但在 DOM 树中它们是独立存在的节点对象。四、使用 createElement 创建新元素createElement 是动态构建 DOM 的核心方法之一。它接受标签名作为参数返回一个尚未挂载到文档树中的元素节点。新创建的元素可以设置属性、添加子节点然后通过 appendChild 插入到文档中。dividpost-areap这是一篇已有的内容。/p/divbuttonidadd-post-btn发布新内容/buttonscriptconstpostAreadocument.getElementById(post-area);constaddPostBtndocument.getElementById(add-post-btn);addPostBtn.addEventListener(click,(){// 创建一个新的 article 元素constnewArticledocument.createElement(article);// 创建标题constheadingdocument.createElement(h3);constheadingTextdocument.createTextNode(新文章 -${newDate().toLocaleTimeString()});heading.appendChild(headingText);// 创建正文段落constparagraphdocument.createElement(p);constparaTextdocument.createTextNode(这是动态生成的文章内容。);paragraph.appendChild(paraText);// 将标题和段落追加到 article 中newArticle.appendChild(heading);newArticle.appendChild(paragraph);// 给 article 添加类名和样式newArticle.classNamedynamic-post;newArticle.style.border1px solid #ccc;newArticle.style.margin10px 0;newArticle.style.padding10px;// 将完整的 article 追加到页面中postArea.appendChild(newArticle);});/scriptcreateElement 创建的节点在挂载之前是完全独立的可以进行各种属性设置和子节点添加操作。这种在内存中先构建完整子树再一次性挂载的方式比逐步向文档中添加节点具有更好的性能因为减少了浏览器的重排次数。五、removeChild 移除节点的正确方式removeChild 方法用于从父节点中移除指定的子节点。被移除的节点仍然存在于内存中可以重新挂载到文档的其他位置实现节点的移动效果。ulidsource-listliiditem-1可移动的项目一/liliiditem-2可移动的项目二/liliiditem-3可移动的项目三/li/ululidtarget-listli目标列表的固定项目/li/ulbuttonidmove-btn移动第一个项目/buttonscriptconstsourceListdocument.getElementById(source-list);consttargetListdocument.getElementById(target-list);constmoveBtndocument.getElementById(move-btn);moveBtn.addEventListener(click,(){// 检查源列表中是否还有可移动的项目if(sourceList.children.length0){constitemToMovesourceList.firstElementChild;// 从源列表中移除sourceList.removeChild(itemToMove);// 追加到目标列表中targetList.appendChild(itemToMove);console.log(已移动${itemToMove.textContent});}else{console.log(源列表已无项目可移动);}});/scriptremoveChild 的调用者是父节点参数是要移除的子节点。被移除的节点并没有被销毁它在 JavaScript 中的引用依然有效。这种机制使得在列表之间移动元素变得非常方便只需先从原父节点移除再追加到新父节点即可。六、通过节点关系深入遍历表格结构当需要从复杂的 DOM 结构中提取特定数据时可以结合多种遍历方法来定位目标节点。childNodes 属性会返回所有类型的子节点包括元素节点和文本节点。tableiddata-tableborder1tbodytrtd姓名/tdtd年龄/tdtd城市/td/trtrtd张三/tdtd28/tdtd北京/td/trtrtd李四/tdtd35/tdtd上海/td/tr/tbody/tablebuttonidextract-btn提取第二行第二列数据/buttonpidresult-display/pscriptconstextractBtndocument.getElementById(extract-btn);constresultDisplaydocument.getElementById(result-display);extractBtn.addEventListener(click,(){// 通过关系链逐层深入到目标单元格constmyTabledocument.getElementById(data-table);// 获取 tbodyconstmyTableBodymyTable.getElementsByTagName(tbody)[0];// 获取第二行索引为 1constmyRowmyTableBody.getElementsByTagName(tr)[1];// 获取第二列索引为 1constmyCellmyRow.getElementsByTagName(td)[1];// childNodes[0] 获取该单元格的第一个子节点通常是文本节点constmyCellTextmyCell.childNodes[0];// 使用 data 属性获取文本节点的内容resultDisplay.textContent提取结果${myCellText.data};console.log(提取的文本内容,myCellText.data);});/scriptchildNodes 与 getElementsByTagName 的一个重要区别是前者返回所有类型的节点包括文本节点和注释节点而后者只返回指定标签名的元素节点。在使用 childNodes 时要注意索引位置可能包含因空白字符产生的文本节点。七、操作属性值与动态控制样式getAttribute 和 setAttribute 是操作元素属性的基本方法。此外通过 style 属性可以直接修改元素的内联样式实现对页面外观的动态控制。tableidstyle-tableborder1tbodytrtd第一列/tdtd第二列/tdtd第三列/td/trtrtd数据 1-1/tdtd数据 1-2/tdtd数据 1-3/td/trtrtd数据 2-1/tdtd数据 2-2/tdtd数据 2-3/td/tr/tbody/tablebuttonidhighlight-btn高亮第一列并隐藏第二列/buttonbuttonidcheck-border-btn查看表格边框属性/buttonscriptconststyleTabledocument.getElementById(style-table);consthighlightBtndocument.getElementById(highlight-btn);constcheckBorderBtndocument.getElementById(check-border-btn);highlightBtn.addEventListener(click,(){// 获取所有行constrowsstyleTable.getElementsByTagName(tr);for(leti0;irows.length;i){constcellsrows[i].getElementsByTagName(td);for(letj0;jcells.length;j){if(j0){// 第一列设置红色背景cells[j].style.background#ffcccc;cells[j].style.fontWeightbold;}elseif(j1){// 第二列隐藏cells[j].style.displaynone;}}}});checkBorderBtn.addEventListener(click,(){// 使用 getAttribute 获取属性值constborderValuestyleTable.getAttribute(border);console.log(表格 border 属性值,borderValue);// 也可以直接通过属性名访问console.log(通过属性访问,styleTable.border);});/script通过 style 对象设置的样式会直接写入元素的内联 style 属性中优先级高于外部样式表。如果需要批量控制样式更好的做法是动态增删 CSS 类名这样可以将样式逻辑与 JavaScript 代码分离便于维护。总结动态构建和更新 DOM 树是前端开发中最基础也最频繁的操作之一。本文通过多个示例梳理了以下核心知识点创建节点的基本流程是自上而下创建、自下而上挂载先构建完整的子树再一次性插入文档可以提升性能。createElement 用于创建任意标签的元素节点createTextNode 用于创建文本内容节点。appendChild 将新节点添加为父节点的最后一个子节点被移除的节点可以通过 removeChild 重新挂载到其他位置。getElementsByTagName 返回动态集合自动反映 DOM 变化childNodes 返回所有类型的子节点包括文本节点。getAttribute 和 setAttribute 用于操作元素的 HTML 属性值而 style 对象可以直接控制内联样式。利用 parentNode 和 childNodes 等关系属性可以沿 DOM 树层层深入精确定位到目标节点并提取其数据。熟练掌握这些基础方法是进行复杂 DOM 操作、实现动态交互效果的前提。在实际项目中合理组合这些方法可以构建出灵活且高性能的用户界面。想要解锁更多HTML 核心标签实战、前端零基础入门干货、开发避坑全指南吗持续关注后续将更新CSS 布局实战、JavaScript 交互基础、全站导航开发等硬核内容带你从新手快速进阶轻松搞定前端开发