DOM树与节点操作:用JS给网页“动手术”

你写的HTML页面,在浏览器眼里其实是一棵树。今天我们就来当一回"外科医生",用JS给这棵树做手术------增、删、改、查,想怎么动就怎么动。看完这篇,你就能理解为什么说"JS能控制网页的一切"。

前言

你有没有想过,当你用document.getElementById拿到一个元素,然后改它的文字、换它的颜色时,背后发生了什么?

其实,浏览器把HTML解析成了一棵"树",每个标签、属性、文本都是树上的一个"节点"。JS能做的,就是在这棵树上爬上爬下,找到某个节点,然后对它做各种操作------换个果子、摘掉枯枝、甚至嫁接新枝。

今天我们就来解剖这棵DOM树,学会用JS给网页"做手术"。

一、DOM树:网页的"族谱"

DOM(Document Object Model)把HTML文档表示成一棵树。比如这段HTML:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>我的网页</title>
  </head>
  <body>
    <div class="container">
      <h1>标题</h1>
      <p>一段文字</p>
    </div>
  </body>
</html>

在浏览器眼里,它长这样:

css 复制代码
html
├── head
│   └── title
│       └── "我的网页"
└── body
    └── div.container
        ├── h1
        │   └── "标题"
        └── p
            └── "一段文字"

每个方框都是一个节点 。节点之间是父子、兄弟关系。这棵树的根节点是document

节点有不同的类型,最常见的是:

  • 元素节点 :比如<div><p>,类型是1
  • 文本节点:比如"标题"这两个字,类型是3
  • 属性节点 :比如class="container",类型是2(但很少单独操作)

二、获取节点:找到你要动刀的位置

做手术第一步,得找到病灶。JS提供了好几种"找节点"的方法:

1. 单个元素

js 复制代码
// 根据ID(最常用)
const header = document.getElementById('header');

// 根据CSS选择器(推荐,灵活)
const container = document.querySelector('.container');
const title = document.querySelector('#title');

// 根据类名(返回集合)
const items = document.getElementsByClassName('item'); // HTMLCollection,实时更新

2. 多个元素

js 复制代码
// 获取所有匹配的元素
const allDivs = document.querySelectorAll('div'); // NodeList,静态快照

// 根据标签名
const paras = document.getElementsByTagName('p'); // HTMLCollection

3. 在节点之间"爬树"

拿到一个节点后,你可以在它周围爬来爬去:

js 复制代码
const container = document.querySelector('.container');

// 往上爬
const parent = container.parentNode;

// 往下爬
const firstChild = container.firstChild; // 可能是文本节点(换行)
const firstElementChild = container.firstElementChild; // 只算元素

// 找兄弟
const prev = container.previousSibling; // 可能是文本节点
const prevElement = container.previousElementSibling;
const next = container.nextElementSibling;

坑点firstChildnextSibling这些会返回文本节点(包括换行和空格),所以大部分时候用firstElementChildnextElementSibling更安全。

三、修改节点:动手术的核心操作

找到目标后,就可以下手了。

1. 修改内容和属性

js 复制代码
// 改文本内容
element.textContent = '新文本'; // 纯文本,安全
element.innerHTML = '<strong>新文本</strong>'; // 解析HTML,有XSS风险

// 改属性
element.id = 'newId';
element.className = 'newClass'; // 覆盖所有类
element.classList.add('active'); // 推荐,增删类
element.classList.remove('hidden');
element.classList.toggle('open');

// 改样式(内联样式)
element.style.color = 'red';
element.style.backgroundColor = '#f0f0f0'; // 驼峰命名

2. 创建新节点

js 复制代码
// 创建元素
const newDiv = document.createElement('div');
newDiv.textContent = '我是新来的';

// 创建文本节点(很少单独用)
const textNode = document.createTextNode('一段文字');

3. 插入节点

js 复制代码
// 追加到最后
parent.appendChild(newDiv);

// 插入到某个子节点之前
parent.insertBefore(newDiv, referenceNode);

// 现代插入方法(更灵活)
referenceNode.before(newDiv); // 插到前面
referenceNode.after(newDiv);  // 插到后面
parent.prepend(newDiv);       // 插到父元素开头
parent.append(newDiv);        // 插到父元素末尾(类似appendChild)

4. 删除节点

js 复制代码
// 删除自己
element.remove();

// 通过父节点删除
parent.removeChild(child);

四、实战:动态添加待办事项

来做个简单待办列表,把上面的操作串起来:

html 复制代码
<div id="todo-app">
  <input type="text" id="todo-input" placeholder="输入待办事项">
  <button id="add-btn">添加</button>
  <ul id="todo-list"></ul>
</div>
js 复制代码
const input = document.getElementById('todo-input');
const addBtn = document.getElementById('add-btn');
const list = document.getElementById('todo-list');

function addTodo() {
  const text = input.value.trim();
  if (text === '') return;
  
  // 创建li元素
  const li = document.createElement('li');
  li.textContent = text;
  
  // 创建删除按钮
  const delBtn = document.createElement('button');
  delBtn.textContent = '删除';
  delBtn.onclick = function() {
    li.remove(); // 删除这一项
  };
  
  li.appendChild(delBtn);
  list.appendChild(li);
  
  input.value = ''; // 清空输入框
}

addBtn.addEventListener('click', addTodo);
// 按回车也添加
input.addEventListener('keypress', function(e) {
  if (e.key === 'Enter') addTodo();
});

就这几行代码,一个动态待办列表就有了。你看,增删改查全用上了。

五、节点集合:HTMLCollection vs NodeList

当你用getElementsByClassName时,拿到的是HTMLCollection ;用querySelectorAll拿到的是NodeList。它们有啥区别?

  • HTMLCollection:实时的。DOM变了,它也跟着变。而且它只有元素节点,没有文本节点。
  • NodeList :大部分是静态快照(querySelectorAll返回的就是静态的)。但childNodes返回的NodeList是实时的。
js 复制代码
const live = document.getElementsByClassName('item'); // 实时
const static = document.querySelectorAll('.item'); // 静态

// 如果你删除了一个.item元素,live会立刻变少,static还是原来的

遍历时,HTMLCollection没有forEach方法(但可以Array.from()转成数组),NodeList有forEach

六、性能小贴士:别频繁动DOM

DOM操作是"重活",频繁操作会影响性能。记住几个原则:

  1. 批量操作 :用document.createDocumentFragment()创建虚拟片段,一次性插入。
js 复制代码
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = i;
  fragment.appendChild(li);
}
list.appendChild(fragment); // 只触发一次重排
  1. 减少重排 :修改样式时,尽量用classList批量改,而不是一个个改style属性。

  2. 离屏操作:先把元素从DOM树上摘下来,改完再放回去。

七、总结:DOM就是你的"手术台"

  • DOM是HTML解析成的树,每个标签、文本都是节点。
  • document.querySelector等方法找到节点。
  • textContentinnerHTML改内容,用classList改样式。
  • createElement造新节点,用appendinsertBefore插入,用remove删除。
  • 注意HTMLCollection和NodeList的区别,实时和静态要分清。
  • 批量操作、减少重排,让页面更流畅。

掌握了这些,你就能用JS随心所欲地操控页面。明天我们将继续深入,聊聊事件流与事件委托------当用户点击按钮时,浏览器里到底发生了什么。

如果你觉得今天的"手术"课够实用,点个赞让更多人看到。我们明天见!

相关推荐
码事漫谈9 小时前
大模型输出的“隐性结构塌缩”问题及对策
前端·后端
怕浪猫9 小时前
2026 年前端工程师面试:一份来自面试官视角的真实复盘
面试
这儿有一堆花10 小时前
前端三件套真的落后了吗?揭开现代 Web 开发的底层逻辑
前端·javascript·css·html5
.Cnn10 小时前
JavaScript 前端基础笔记(网页交互核心)
前端·javascript·笔记·交互
醉酒的李白、10 小时前
Vue3 组件通信本质:Props 下发,Emits 回传
前端·javascript·vue.js
anOnion11 小时前
构建无障碍组件之Window Splitter Pattern
前端·html·交互设计
NotFound48611 小时前
实战分享Python爬虫,如何实现高效解析 Web of Science 文献数据并导出 CSV
前端·爬虫·python
徐小夕11 小时前
PDF无限制预览!Jit-Viewer V1.5.0开源文档预览神器正式发布
前端·vue.js·github
WangJunXiang611 小时前
Haproxy搭建Web群集
前端
吴声子夜歌12 小时前
Vue.js——自定义指令
前端·vue.js·flutter