03-DOM与BOM操作
📋 学习目标
掌握DOM操作核心API
学习事件处理机制
理解BOM浏览器对象
掌握性能优化技巧
🌳 DOM操作
元素选择
// getElementById
const element = document.getElementById('myId');
// querySelector(推荐)
const element = document.querySelector('.class');
const element = document.querySelector('#id');
const element = document.querySelector('[data-id="123"]');
// querySelectorAll
const elements = document.querySelectorAll('.items');
// 返回NodeList,可以forEach
elements.forEach(el => console.log(el));
// 转换为数组
const array = Array.from(elements);
const array = [...elements];
// 其他选择器(不推荐)
document.getElementsByClassName('class');
document.getElementsByTagName('div');
document.getElementsByName('name');
元素创建与插入
// 创建元素
const div = document.createElement('div');
div.className = 'container';
div.id = 'main';
div.textContent = 'Hello';
// 设置HTML
div.innerHTML = '<span>Content</span>';
// 设置属性
div.setAttribute('data-id', '123');
div.dataset.id = '123'; // 推荐
// 插入元素
document.body.appendChild(div);
document.body.append(div, 'text', anotherElement);
// 在前面插入
parent.insertBefore(newNode, referenceNode);
parent.prepend(newNode);
// 在后面插入
parent.after(newNode);
// 替换
parent.replaceChild(newNode, oldNode);
// 移除
element.remove();
parent.removeChild(child);
// 克隆
const clone = element.cloneNode(true); // true表示深拷贝
元素属性操作
// 类名操作
element.classList.add('active');
element.classList.remove('active');
element.classList.toggle('active');
element.classList.contains('active');
element.classList.replace('old', 'new');
// 样式操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.cssText = 'color: red; font-size: 16px;';
// 获取计算样式
const styles = window.getComputedStyle(element);
const color = styles.getPropertyValue('color');
// 属性操作
element.getAttribute('data-id');
element.setAttribute('data-id', '123');
element.removeAttribute('data-id');
element.hasAttribute('data-id');
// dataset(推荐)
element.dataset.userId = '123'; // <div data-user-id="123">
const userId = element.dataset.userId;
DOM遍历
// 父元素
element.parentElement;
element.parentNode;
// 子元素
element.children; // HTMLCollection
element.childNodes; // NodeList(包含文本节点)
element.firstElementChild;
element.lastElementChild;
// 兄弟元素
element.nextElementSibling;
element.previousElementSibling;
// 最近的匹配元素
element.closest('.container');
// 检查包含关系
parent.contains(child);
// 遍历所有子元素
for (const child of element.children) {
console.log(child);
}
📅 事件处理
事件监听
// 添加事件监听
element.addEventListener('click', function(e) {
console.log('Clicked!', e);
});
// 箭头函数(注意this指向)
element.addEventListener('click', (e) => {
console.log(e.target);
});
// 移除事件监听
function handleClick(e) {
console.log('Clicked');
}
element.addEventListener('click', handleClick);
element.removeEventListener('click', handleClick);
// 只执行一次
element.addEventListener('click', handleClick, {once: true});
// 捕获阶段
element.addEventListener('click', handleClick, {capture: true});
// 阻止默认行为
element.addEventListener('click', (e) => {
e.preventDefault();
});
// 阻止冒泡
element.addEventListener('click', (e) => {
e.stopPropagation();
});
事件委托
// ❌ 为每个元素添加监听
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ 事件委托(推荐)
document.querySelector('.list').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleItemClick(e.target);
}
});
// 实际应用
const list = document.querySelector('.todo-list');
list.addEventListener('click', (e) => {
const target = e.target;
if (target.matches('.delete-btn')) {
const item = target.closest('.todo-item');
item.remove();
}
if (target.matches('.toggle-btn')) {
const item = target.closest('.todo-item');
item.classList.toggle('completed');
}
});
常用事件
// 鼠标事件
element.addEventListener('click', handler);
element.addEventListener('dblclick', handler);
element.addEventListener('mousedown', handler);
element.addEventListener('mouseup', handler);
element.addEventListener('mousemove', handler);
element.addEventListener('mouseenter', handler);
element.addEventListener('mouseleave', handler);
element.addEventListener('mouseover', handler);
element.addEventListener('mouseout', handler);
// 键盘事件
element.addEventListener('keydown', (e) => {
console.log(e.key, e.code, e.keyCode);
if (e.key === 'Enter') {
// 处理回车
}
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
// Ctrl+S保存
}
});
element.addEventListener('keyup', handler);
element.addEventListener('keypress', handler); // 已废弃
// 表单事件
input.addEventListener('input', (e) => {
console.log(e.target.value);
});
input.addEventListener('change', handler);
input.addEventListener('focus', handler);
input.addEventListener('blur', handler);
form.addEventListener('submit', (e) => {
e.preventDefault();
// 处理提交
});
// 窗口事件
window.addEventListener('load', handler); // 页面完全加载
window.addEventListener('DOMContentLoaded', handler); // DOM加载完成
window.addEventListener('beforeunload', (e) => {
e.returnValue = ''; // 离开页面提示
});
window.addEventListener('resize', handler);
window.addEventListener('scroll', handler);
自定义事件
// 创建自定义事件
const event = new CustomEvent('myEvent', {
detail: {
message: 'Hello',
value: 123
},
bubbles: true,
cancelable: true
});
// 监听自定义事件
element.addEventListener('myEvent', (e) => {
console.log(e.detail.message);
});
// 触发事件
element.dispatchEvent(event);
// 实际应用:组件通信
class Component {
constructor(element) {
this.element = element;
}
emit(eventName, data) {
const event = new CustomEvent(eventName, {
detail: data,
bubbles: true
});
this.element.dispatchEvent(event);
}
on(eventName, handler) {
this.element.addEventListener(eventName, handler);
}
}
const comp = new Component(document.querySelector('.component'));
comp.on('update', (e) => {
console.log('Updated:', e.detail);
});
comp.emit('update', {value: 'new value'});
🌐 BOM操作
window对象
// 窗口尺寸
window.innerWidth; // 视口宽度
window.innerHeight; // 视口高度
window.outerWidth; // 浏览器窗口宽度
window.outerHeight; // 浏览器窗口高度
// 滚动
window.scrollX; // 水平滚动距离
window.scrollY; // 垂直滚动距离
window.scrollTo(0, 100); // 滚动到指定位置
window.scrollTo({
top: 100,
behavior: 'smooth' // 平滑滚动
});
window.scrollBy(0, 100); // 相对滚动
// 打开新窗口
const newWindow = window.open(
'https://example.com',
'_blank',
'width=800,height=600'
);
// 关闭窗口
newWindow.close();
// 对话框
window.alert('提示信息');
const result = window.confirm('确认吗?');
const input = window.prompt('请输入:', '默认值');
location对象
// URL信息
location.href; // 完整URL
location.protocol; // 协议(http:)
location.host; // 主机名和端口
location.hostname; // 主机名
location.port; // 端口
location.pathname; // 路径
location.search; // 查询字符串(?key=value)
location.hash; // 锚点(#section)
// 导航
location.href = 'https://example.com'; // 跳转
location.assign('https://example.com'); // 跳转
location.replace('https://example.com'); // 替换(不产生历史记录)
location.reload(); // 刷新
location.reload(true); // 强制从服务器刷新
// URLSearchParams
const params = new URLSearchParams(location.search);
params.get('id'); // 获取参数
params.set('page', '2'); // 设置参数
params.delete('old'); // 删除参数
params.toString(); // 转为字符串
// 实际应用
function getQueryParams() {
const params = new URLSearchParams(location.search);
const result = {};
for (const [key, value] of params) {
result[key] = value;
}
return result;
}
history对象
// 导航
history.back(); // 后退
history.forward(); // 前进
history.go(-2); // 后退2页
history.go(1); // 前进1页
// HTML5 History API
history.pushState({page: 1}, 'title', '/page1');
history.replaceState({page: 2}, 'title', '/page2');
// 监听历史变化
window.addEventListener('popstate', (e) => {
console.log('State:', e.state);
});
// 实际应用:单页应用路由
class Router {
constructor() {
this.routes = {};
window.addEventListener('popstate', () => {
this.handleRoute();
});
}
route(path, handler) {
this.routes[path] = handler;
}
navigate(path) {
history.pushState({}, '', path);
this.handleRoute();
}
handleRoute() {
const path = location.pathname;
const handler = this.routes[path];
if (handler) handler();
}
}
screen对象
screen.width; // 屏幕宽度
screen.height; // 屏幕高度
screen.availWidth; // 可用宽度
screen.availHeight; // 可用高度
screen.colorDepth; // 颜色深度
screen.pixelDepth; // 像素深度
⚡ 性能优化
防抖和节流
// 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
const handleInput = debounce((e) => {
console.log('Search:', e.target.value);
}, 500);
input.addEventListener('input', handleInput);
// 节流
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, args);
}
};
}
// 使用
const handleScroll = throttle(() => {
console.log('Scrolled');
}, 200);
window.addEventListener('scroll', handleScroll);
DocumentFragment
// ❌ 多次操作DOM
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
ul.appendChild(li); // 触发1000次重排
}
// ✅ 使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment); // 只触发1次重排
IntersectionObserver
// 懒加载图片
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
// 无限滚动
const sentinel = document.querySelector('.sentinel');
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMore();
}
});
observer.observe(sentinel);
📚 实践练习
练习1:Todo列表
实现功能:
添加、删除Todo
标记完成
过滤显示
本地存储
练习2:图片懒加载
实现:
IntersectionObserver
占位图
加载动画
练习3:无限滚动
实现:
滚动加载
加载状态
错误处理