ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

前端DOM操作全攻略:从元素查找到性能优化的系统拆解

2026/9/19 12:51:15 拓冰建站 浏览量
前端DOM操作全攻略:从元素查找到性能优化的系统拆解 作为前端开发者如果让我说出几个日常写代码用到最频繁的APIDOM操作一定榜上有名。不管你是用原生JS还是用Vue、React最终落到浏览器页面上的每一个增删改、每一次点击反馈底层都离不开DOM的读写操作。很多人会问我“常见的DOM操作到底有哪些”这个问题看起来很基础但真正能系统回答清楚的人其实不多——因为大家平时都是“用到什么查什么”很少停下来把整个DOM操作体系梳理一遍。这篇内容我就把日常前端开发中最常用到的DOM操作做一个系统的拆解从元素查找、内容修改、属性操作到节点增删再到常见的性能坑和监听方案全部用实际例子过一遍。无论你是刚开始学前端的新手还是已经写了一段业务代码、想把自己脑子里零散的DOM知识理清楚的开发者这篇都值得认真看一遍。顺便说一句搜索“DOM”的时候你会发现它其实有两个完全不相关的世界一个是前端工程师每天打交道的Document Object Model文档对象模型另一个是测绘和GIS领域的Digital Orthophoto Map数字正射影像图像ArcMap里的DOM切片、本地切片说的就是后者。这篇文章我们只聊前端的DOM操作GIS那套今天不展开但知识点是死的、思路是活的两边的核心逻辑其实都是“把数据组织成可操作的结构”理解了这一点很多技术学起来都会快很多。1. DOM到底是什么先把底层逻辑盘清楚1.1 DOM就是浏览器给JavaScript的一张“对象地图”很多新手学DOM的时候会背一堆API但始终搞不清楚为什么document.getElementById能拿到元素、为什么改了innerHTML页面就会变。其实核心就一句话浏览器把HTML文档解析成一棵由节点对象组成的树JavaScript通过操作这些节点对象来改变页面。这就是“文档对象模型”。你可以把DOM理解成一张“对象地图”。HTML里的每一个标签、每一段文本、每一个属性都会被浏览器翻译成一个可编程的对象节点比如div变成了HTMLDivElement对象一段文字变成了Text节点。这些节点按照上下级关系组成一棵树树的根是document往下依次是html、head、body再往下是各种元素节点、文本节点、注释节点。有了这张地图JavaScript就像拿到了一份“施工图”。你想改哪个位置的文字先在地图上找到对应的节点然后用API改它浏览器检测到节点变化后就会自动重新渲染页面。这个过程不需要你手动去操作浏览器底层也不需要理解浏览器是怎么绘制像素的你只需要告诉DOM“我要改哪个对象、改成什么样”。这套机制的巧妙之处在于DOM把HTML的“结构”和JavaScript的“行为”彻底打通了。HTML负责定义页面结构但它是静态的JavaScript通过DOM让结构可以动态变化页面才有了交互。换句话说没有DOM前端页面就是一张写死的海报有了DOM页面才变成一个可以实时响应的应用程序。1.2 元素节点、属性节点和文本节点别再混为一谈做DOM操作之前必须先分清几种最基础的节点类型。我在面试前端候选人的时候经常问“createTextNode和createElement有什么区别”能真正说清楚的人不到一半。这其实是个很基础的概念但很多人写业务代码时已经习惯用innerHTML一把梭根本不关心节点的类型。DOM节点主要分这么几类元素节点也就是HTML标签对应出来的节点比如div、p、span。这类节点可以有子节点也可以挂属性和事件。文本节点元素里的纯文字内容属于文本节点比如p你好/p里的“你好”。文本节点没有子节点也不能挂属性和事件。属性节点在DOM规范里属性曾经被视为节点的一种但现在操作属性更多使用getAttribute/setAttribute这类方法属性是元素对象的一部分不再强调“节点”概念。注释节点HTML注释也会生成节点一般操作很少碰到但用遍历API遍历子节点时会遇到需要注意过滤。为什么要区分节点类型因为不同API作用在不同类型的节点上行为完全不同。比如childNodes会返回所有子节点包括文本节点和注释节点而children只返回元素子节点。parentNode和parentElement看起来都能拿到父节点但如果父节点是documentparentElement会返回nullparentNode还能拿到document。这些细节都是实际开发中容易踩坑的地方。还有一点元素节点和文本节点的关系是“树状”的不是“线状”的。一个div里可以嵌套另一个div这棵树可以无限嵌套下去。所以你操作DOM的时候本质上是在这棵树里做“定位-修改”的操作定位越准、修改越小代码就越高效。2. 常见的DOM操作逐个拆解查找、修改、增删、样式2.1 查找元素querySelector系列到底香在哪所有DOM操作的第一步几乎都是“找到要操作的元素”。早期前端开发主要用getElementById、getElementsByClassName、getElementsByTagName这一组API。现在开发中我用的最多的其实是querySelector和querySelectorAll这两个。// 老派写法 const btn document.getElementById(submitBtn); const items document.getElementsByClassName(item); const divs document.getElementsByTagName(div); // 现代推荐写法 const btn document.querySelector(#submitBtn); const items document.querySelectorAll(.item); const divs document.querySelectorAll(div);querySelector系列之所以好用核心在于它接受任何CSS选择器字符串。这意味着你平时写CSS用的那套选择器语法——类名、ID、属性选择器、伪类、嵌套选择器——全部可以直接用在JS里。比如想找“表单里第一个disabled状态的input”用getElementById那套得先拿到表单、再遍历input、再判断disabled而querySelector一行就搞定const firstDisabledInput document.querySelector(form input:disabled);用querySelector时有两个点必须注意。第一querySelector返回的是第一个匹配的元素如果没有匹配到任何元素返回的是null。所以拿到的结果要判空再使用否则直接访问.style或者.textContent会报TypeError。第二getElementsByClassName和getElementsByTagName返回的是HTMLCollectionquerySelectorAll返回的是NodeList。前者是“动态集合”后者在大多数情况下是“静态快照”。这意味着如果你先querySelectorAll拿到一组元素然后往DOM里新增了匹配的元素这个NodeList不会更新但getElementsByClassName的结果会自动包含新加进去的元素。这个差异平时写代码可能感受不到但如果要做动态列表渲染理解它非常重要。2.2 读写内容textContent和innerHTML别再乱用找到元素之后最常做的操作就是读取或者修改里面的内容。这一步有两个API容易混textContent和innerHTML。// textContent纯文本方式读取/写入 const title document.querySelector(.title); console.log(title.textContent); title.textContent 新的标题文字; // innerHTMLhtml片段方式读取/写入 const content document.querySelector(.content); console.log(content.innerHTML); content.innerHTML p带标签的stronghtml/strong内容/p;这两个API最核心的区别在于textContent只会处理纯文本如果你塞进去一个bhi/b页面会原样显示一对尖括号的字符串不会渲染成加粗文字而innerHTML会把这串字符串当作HTML解析直接渲染出加粗的“hi”。这个区别在富文本编辑器、评论列表、动态模板这类场景里特别关键。用innerHTML确实方便但有一个老生常谈的安全隐患——XSS注入。如果你往innerHTML里插入的内容包含用户输入用户完全可以输入一段img srcx onerroralert(1)这样的内容浏览器会执行其中的JavaScript代码。这就是热词里“dom型xss”的核心成因攻击面不在服务器端而在客户端直接把不可信数据交给innerHTML这类API去解析。所以我的习惯是能不用innerHTML就不用除非内容是可信的静态模板。要插入纯文本一定用textContent或者insertAdjacentText要动态创建带结构的元素用createElement配合appendChild。如果实在要用innerHTML先把用户输入里的、、、、做HTML转义。另外还有一个API容易被忽略insertAdjacentHTML。它四个位置参数beforebegin、afterbegin、beforeend、afterend非常灵活比拼innerHTML再重设整个容器要精准得多而且性能更好因为只插入新节点不会影响容器内部已有的节点状态。2.3 操作属性classList是隐藏的效率神器操作元素的HTML属性最常见的有三块通用属性id、title、href、src等、自定义属性data-*、class类名。分别对应的API也不一样// 通用属性 const link document.querySelector(a); link.getAttribute(href); // 读取 link.setAttribute(href, /new-path); // 设置 link.hasAttribute(target); // 判断 link.removeAttribute(target); // 删除 // 标准属性也可以直接通过对象属性操作 link.href /new-path; link.id mainLink;很多属性在DOM对象上有对应的属性名比如href、id、src、value直接用element.href xxx也能生效。这里有个坑属性名和对象属性名有时不一样比如class对应的是classNamefor对应的是htmlFor。用getAttribute/setAttribute就完全没有这个问题因为它们直接用字符串传属性名。class类名操作我强烈建议直接上classList不要手动拼className字符串。classList提供了add、remove、toggle、contains、replace五个方法语义清晰还自带容错const box document.querySelector(.box); // 添加类名重复添加不会生效 box.classList.add(active); // 移除类名 box.classList.remove(active); // 切换类名适合做暗黑模式这类开关 box.classList.toggle(dark-mode); // 判断是否包含类名 if (box.classList.contains(active)) { // 做点什么 } // 替换类名 box.classList.replace(active, done);classList.toggle还支持第二个参数可以强制指定添加到还是移除。比如某个状态只有在用户登录时才需要高亮可以写box.classList.toggle(highlight, isLoggedIn)判断逻辑一行搞定。还有data-*自定义属性的读写新浏览器环境里直接可以用datasetdiv iduser>