1、DOM 基础概念
DOM (Document Object Model,文档对象模型) 是 JavaScript 操作网页内容的核心 API。它将 HTML 文档转换为一个由节点组成的树状结构,允许通过 JavaScript 动态地访问、修改、添加或删除网页中的任何元素。
2、DOM 树结构
当浏览器加载 HTML 页面时,它会将文档解析为一个由节点组成的树状结构
document (根节点)
├── html (元素节点)
│ ├── head (元素节点)
│ │ ├── title (元素节点)
│ │ └── meta (元素节点)
│ └── body (元素节点)
│ ├── h1 (元素节点)
│ ├── p (元素节点)
│ ├── div (元素节点)
│ │ └── img (元素节点)
│ └── form (元素节点)
│ ├── input (元素节点)
│ └── button (元素节点)
│ └── ...
3、DOM 的核心对象
1、document 对象
作用:document
是 DOM 的入口点,代表整个 HTML 文档,就代表当前当前网页内容
常用属性和方法:
// 获取文档信息
document.title; // 获取/设置页面标题
document.URL; // 获取完整 URL
document.domain; // 获取域名
document.referrer; // 获取来源页面
// 查找元素
document.getElementById('id'); // 通过ID获取元素
document.getElementsByClassName('class'); // 通过类名获取元素集合
document.getElementsByTagName('h1'); // 通过标签名获取元素集合
document.querySelector('css选择器'); // 获取第一个匹配元素
document.querySelectorAll('css选择器'); // 获取所有匹配元素集合
var father = document.getElementById('fater ');
var childrens = father.children; // 获取父级下的所有子级元素集合
// 创建和操作文档
document.createElement('tagName'); // 创建新元素
document.createTextNode('text'); // 创建文本节点
document.createComment('comment'); // 创建注释节点
2、Element 对象
作用:代表 HTML 元素,是 DOM 树中最常用的对象类型,也就是通过document对象得到的元素对象
常用属性和方法:
// 获取和设置内容
element.innerHTML; // 获取/设置HTML内容(会自动解析HTML标签)
element.textContent; // 获取/设置纯文本内容
element.innerText; // 获取/设置可见文本内容(考虑CSS隐藏)
// 获取元素信息
element.id; // 元素ID
element.className; // 元素类名
element.classList; // 类列表对象(推荐操作类)
element.tagName; // 元素标签名(大写)
element.attributes; // 元素所有属性集合
// 操作样式
element.style; // 元素内联样式对象
element.style.color = 'red'; // 设置内联样式
... //所有的样式都是操作
// 获取元素位置和尺寸
element.offsetParent; // 最近的定位父元素
element.offsetTop; // 相对于offsetParent的顶部距离
element.offsetLeft; // 相对于offsetParent的左侧距离
element.offsetWidth; // 元素宽度(含边框和内边距)
element.offsetHeight; // 元素高度(含边框和内边距)
element.getBoundingClientRect(); // 获取元素相对于视口的位置和尺寸
// 操作DOM
element.appendChild(newChild); // 添加子元素
element.removeChild(child); // 删除子元素 必须通过父级删除
element.replaceChild(newChild, oldChild); // 替换子元素
element.insertBefore(newNode, referenceNode); // 在指定节点前插入
element.remove(); // 删除自身(现代方法)
element.cloneNode(deep); // 克隆节点(deep=true克隆后代)
例:操作节点~增、删、改、查
1、追加节点
//初始代码
<body>
<p id="p2">p2</p>
<div id="myDiv">
<p id="s1">s1</p>
<h1 id="h1">h1</h1>
<p id="p1">p1</p>
</div>
</body>
<script>
var father = document.getElementById("myDiv");
var p2 = document.getElementById("p2");
father.appendChild(p2) //将p2追加到myDiv的子级中,默认追加到最后一个(是直接移动整个标签)
</script>
追加后的效果
2、修改节点,也是一种节点插入,当有父级节点的时候修改父级内容,就会生成一个新的节点并且覆盖旧的子级节点,所有在修改父级节点的时候一定要注意这个问题
//动态赋值以及修改样式
<body>
<p id="ss"></p>
</body>
<script>
var s = document.getElementById("ss")
s.innerText = "123"; //页面展示123
s.innerHTML = "<strong>456</strong>" //页面展示粗体456
s.style.color = 'red'; //页面展示红色粗体456
//所以上述element代表的就是此处的s
</script>
3、删除节点
//删除节点
<body>
<div id="myDiv">
<p id="ss">11</p>
<h1 id="h1">22</h1>
<p id="p1">33</p>
</div>
</body>
<script>
var myDiv = document.getElementById("myDiv"); //父节点
var h1 = document.getElementById("h1");
myDiv.removeChild(h1);
myDiv.removeChild(myDiv.children[0]); //删除,注意此种方式多次删除时,children的下标是动态变的
</script>
//删除节点方式二
<body>
<div id="myDiv">
<p id="ss">11</p>
<h1 id="h1">22</h1>
<p id="p1">33</p>
</div>
</body>
<script>
var h1 = document.getElementById("h1");
var myDiv = h1.parentElement; //找父节点
myDiv.removeChild(h1); //删除
</script>
//删除节点方式三
<body>
<div id="myDiv">
<p id="ss">11</p>
<h1 id="h1">22</h1>
<p id="p1">33</p>
</div>
</body>
<script>
var h1 = document.getElementById("h1");
h1.remove(); //删除
</script>
3、Node 对象
作用:所有DOM节点的基类,包括元素节点、文本节点、注释节点等
常用节点类型常量:
Node.ELEMENT_NODE (1) - 元素节点
Node.TEXT_NODE (3) - 文本节点
Node.COMMENT_NODE (8) - 注释节点
Node.DOCUMENT_NODE (9) - 文档节点
常用属性和方法:
// 节点关系
node.parentNode; // 父节点
node.childNodes; // 所有子节点集合
node.firstChild; // 第一个子节点
node.lastChild; // 最后一个子节点
node.nextSibling; // 下一个兄弟节点
node.previousSibling; // 上一个兄弟节点
// 节点操作
node.appendChild(newNode); // 添加子节点
node.insertBefore(newNode, referenceNode); // 在指定节点前插入
node.removeChild(child); // 删除子节点
node.replaceChild(newChild, oldChild); // 替换子节点
node.contains(otherNode); // 是否包含指定节点
node.hasChildNodes(); // 是否有子节点
4、其他重要DOM对象
1、NodeList 和 HTMLCollection
NodeList: 类数组对象,表示节点集合(如 querySelectorAll
返回)
HTMLCollection: 类数组对象,表示元素集合(如 getElementsByClassName
返回)
// 遍历NodeList/HTMLCollection
const elements = document.querySelectorAll('.test');
elements.forEach(el => {
console.log(el.textContent);
});
// 或传统方式
for (let i = 0; i < elements.length; i++) {
console.log(elements[i].textContent);
}
2、Attribute 对象
代表元素的属性
// 获取和设置属性
element.getAttribute('name'); // 获取自定义属性
element.setAttribute('name', '哈哈');// 设置自定义属性
element.removeAttribute('name'); // 删除属性
element.hasAttribute('name'); // 检查属性是否存在
// 直接访问标准属性
element.name= '哈哈'; // 等同于setAttribute
element.age= '18'; // 等同于setAttribute
3、Event 对象
代表事件相关信息的对象
// 事件监听示例
element.addEventListener('click', function(event) {
console.log(event.target); // 触发事件的元素
console.log(event.type); // 事件类型
console.log(event.clientX); // 鼠标X坐标
console.log(event.clientY); // 鼠标Y坐标
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
5、DOM 操作实践
1、基本DOM操作示例 :
<div id="container">
<ul id="list">
<li class="item">测试1</li>
<li class="item">测试2</li>
</ul>
</div>
<script>
// 1. 获取元素
const container = document.getElementById('container');
const list = document.getElementById('list');
const items = document.querySelectorAll('.item');
// 2. 修改内容
list.innerHTML = '<li class="item">新测试</li>'; // 替换所有子元素
// 3. 创建新元素
const newItem = document.createElement('li');
newItem.className = 'item';
newItem.textContent = '动态创建的测试';
// 4. 添加元素
list.appendChild(newItem); // 添加到末尾
// 5. 插入元素到指定位置
const referenceItem = list.children[0]; //要通过父级找到子级节点,才能实现insertBefore的指定插入
list.insertBefore(newItem, referenceItem); // 在referenceItem前插入
// 6. 删除元素
const oldItem = items[1];
oldItem.remove(); // 现代方法
// 或 list.removeChild(oldItem);
// 7. 克隆元素
const clonedItem = newItem.cloneNode(true); // true表示深度克隆
list.appendChild(clonedItem);
</script>
2、表单简单操作示例:
//监听表单方式
<form id="myForm">
<input type="text" id="username" placeholder="用户名">
<input type="password" id="password" placeholder="密码">
<button type="submit">提交</button>
</form>
<script>
const form = document.getElementById('myForm');
const usernameIn = document.getElementById('username');
const passwordIn = document.getElementById('password');
// 监听表单提交
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交
const username = usernameIn.value;
const password = passwordIn.value;
console.log('用户名:', username);
console.log('密码:', password);
// 表单验证示例
if (!username || !password) {
alert('请填写所有字段');
return;
}
// 处理表单数据...
alert('表单提交成功!');
});
// 实时验证
usernameIn.addEventListener('input', function() {
if (this.value.length < 3) {
this.style.borderColor = 'red';
} else {
this.style.borderColor = 'green';
}
});
</script>
//表单提交事件方式
<form id="myForm">
<input type="text" id="username" placeholder="用户名">
<input type="password" id="password" placeholder="密码">
<button type="submit" onclick="sub()">提交</button>
</form>
<script>
const usernameIn = document.getElementById('username');
const passwordIn = document.getElementById('password');
// 表单提交事件
function sub() {
event.preventDefault(); // 阻止表单默认提交
const username = usernameIn.value;
const password = passwordIn.value;
console.log('用户名:', username);
console.log('密码:', password);
// 表单验证示例
if (!username || !password) {
alert('请填写所有字段');
return;
}
// 处理表单数据...
alert('表单提交成功!');
}
// 实时验证
usernameIn.addEventListener('input', function () {
if (this.value.length < 3) {
this.style.borderColor = 'red';
} else {
this.style.borderColor = 'green';
}
});
</script>
6、DOM 高级特性
1、事件委托
利用事件冒泡机制,在父元素上监听子元素的事件
<ul id="todoList">
<li>任务1 <button class="delete">删除</button></li>
<li>任务2 <button class="delete">删除</button></li>
<li>任务3 <button class="delete">删除</button></li>
</ul>
<script>
const todoList = document.getElementById('todoList');
// 事件委托 - 在父元素上监听子按钮的点击
todoList.addEventListener('click', function(event) {
if (event.target.classList.contains('delete')) {
const listItem = event.target.parentElement;
listItem.remove();
}
});
</script>
2、动态加载内容
使用 Fetch API 或 AJAX 动态加载数据并更新DOM
<div id="content"></div>
<button id="loadData">加载数据</button>
<script>
document.getElementById('loadData').addEventListener('click', function() {
// 使用Fetch API获取数据
fetch('http://localhost:8080/test/posts/1')
.then(response => response.json())
.then(data => {
const contentDiv = document.getElementById('content');
contentDiv.innerHTML = `
<h2>${data.title}</h2>
<p>${data.body}</p>
`;
})
.catch(error => {
console.error('加载数据出错:', error);
});
});
</script>
3、自定义数据属性 (data-*)
HTML5 提供的自定义数据属性,可通过 dataset 访问
<div id="user" data-id="123" data-role="admin" data-active="true">
用户信息
</div>
<script>
const userDiv = document.getElementById('user');
// 获取data属性
console.log(userDiv.dataset.id); // "123"
console.log(userDiv.dataset.role); // "admin"
console.log(userDiv.dataset.active); // "true"
// 设置data属性
userDiv.dataset.role = 'user';
userDiv.dataset.lastLogin = '2023-01-01';
console.log(userDiv.dataset.lastLogin); // "2023-01-01"
</script>
7、DOM 性能优化
1、减少DOM操作
DOM 操作是昂贵的,应尽量减少直接操作
// 每次循环都直接操作DOM - 性能差
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
优化:
// 先在内存中构建,然后一次性添加 - 性能好
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
document.body.appendChild(fragment);
2、使用 DocumentFragment
DocumentFragment 是轻量级的文档对象,用于临时存储DOM节点,减少重排和重绘。
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const li = document.createElement('li');
li.textContent = `项目 ${i}`;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
3、批量修改样式
避免频繁读写样式属性,应批量修改。
// 每次循环都查询和修改样式 - 触发多次重排
for (let i = 0; i < elements.length; i++) {
elements[i].style.left = i * 10 + 'px';
elements[i].style.top = i * 10 + 'px';
}
优化:
// 使用CSS类或一次性修改style
elements.forEach((el, i) => {
el.style.cssText = `left: ${i * 10}px; top: ${i * 10}px;`;
});
// 或更好的方式 - 使用CSS类
elements.forEach(el => {
el.classList.add('new-position');
});
8、DOM API
1、MutationObserver
监听DOM树的变化。
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log('DOM发生了变化:', mutation.type);
if (mutation.type === 'childList') {
console.log('子节点发生变化');
}
});
});
// 开始观察目标节点
observer.observe(document.body, {
childList: true, // 观察子节点的添加或删除
subtree: true, // 观察所有后代节点
attributes: true, // 观察属性变化
attributeOldValue: true // 记录旧属性值
});
// 停止观察
// observer.disconnect();
2、 IntersectionObserver
监听元素与视口的交叉状态,常用于懒加载。
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('元素进入视口:', entry.target);
// 执行加载操作...
entry.target.classList.add('loaded');
observer.unobserve(entry.target); // 停止观察已加载的元素
}
});
});
// 观察元素
document.querySelectorAll('.lazy-image').forEach(img => {
observer.observe(img);
});