你写的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;
坑点 :firstChild、nextSibling这些会返回文本节点(包括换行和空格),所以大部分时候用firstElementChild、nextElementSibling更安全。
三、修改节点:动手术的核心操作
找到目标后,就可以下手了。
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操作是"重活",频繁操作会影响性能。记住几个原则:
- 批量操作 :用
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); // 只触发一次重排
-
减少重排 :修改样式时,尽量用
classList批量改,而不是一个个改style属性。 -
离屏操作:先把元素从DOM树上摘下来,改完再放回去。
七、总结:DOM就是你的"手术台"
- DOM是HTML解析成的树,每个标签、文本都是节点。
- 用
document.querySelector等方法找到节点。 - 用
textContent、innerHTML改内容,用classList改样式。 - 用
createElement造新节点,用append、insertBefore插入,用remove删除。 - 注意HTMLCollection和NodeList的区别,实时和静态要分清。
- 批量操作、减少重排,让页面更流畅。
掌握了这些,你就能用JS随心所欲地操控页面。明天我们将继续深入,聊聊事件流与事件委托------当用户点击按钮时,浏览器里到底发生了什么。
如果你觉得今天的"手术"课够实用,点个赞让更多人看到。我们明天见!