1. let 和 const 的声明时的区别
1.1 变量声明的发展历程
在 JavaScript 发展过程中,变量的声明方式经历了三个阶段:
// ES5 之前: 使用 var
var name = '张三';
var age = 18;
// ES6 (2015): 新增 let 和 const
let name = '张三';
const age = 18;
// 现代开发: 推荐使用 let 和 const,避免使用 var1.2 let 和 const 的基本区别
| 特性 | let | const |
|---|---|---|
| 可变性 | 可以重新赋值 | 声明后不可重新赋值 |
| 初始化 | 可以不赋初始值 | 必须赋初始值 |
| 块级作用域 | ✅ | ✅ |
| 暂时性死区 | ✅ | ✅ |
| 重复声明 | ❌ 不允许 | ❌ 不允许 |
| 使用场景 | 需要变化的变量 | 不变的常量 |
1.3 const 的特点
1.3.1 必须初始化
const 声明的变量必须立即初始化,否则会报错:
// ❌ 错误: 缺少初始值
const name;
// SyntaxError: Missing initializer in const declaration
// ✅ 正确: 声明时必须赋值
const name = '张三';
const age = 18;
const PI = 3.141592653;1.3.2 不可重新赋值
const 声明的变量不能重新赋值:
const name = '张三';
// ❌ 错误: 不能重新赋值
name = '李四';
// TypeError: Assignment to constant variable.
const age = 18;
age = 19;
// TypeError: Assignment to constant variable.1.3.3 const 声明对象的注意事项
重要: const 声明的对象本身不能被重新赋值,但对象内部的属性可以被修改:
// 声明对象
const person = {
name: '张三',
age: 18
};
// ✅ 正确: 可以修改对象的属性
person.name = '李四';
person.age = 19;
console.log(person); // { name: '李四', age: 19 }
// ✅ 正确: 可以添加新属性
person.city = '北京';
console.log(person); // { name: '李四', age: 19, city: '北京' }
// ✅ 正确: 可以删除属性
delete person.city;
console.log(person); // { name: '李四', age: 19 }
// ❌ 错误: 不能重新赋值整个对象
person = { name: '王五', age: 20 };
// TypeError: Assignment to constant variable.数组同理:
const numbers = [1, 2, 3, 4, 5];
// ✅ 正确: 可以修改数组内容
numbers.push(6);
numbers[0] = 10;
numbers.pop();
console.log(numbers); // [10, 2, 3, 4]
// ❌ 错误: 不能重新赋值整个数组
numbers = [1, 2, 3];
// TypeError: Assignment to constant variable.1.4 let 的特点
1.4.1 可以不初始化
let 声明的变量可以不立即赋值:
// ✅ 正确: 可以不赋初始值
let name;
name = '张三';
console.log(name); // 张三
let age;
console.log(age); // undefined
age = 18;
console.log(age); // 181.4.2 可以重新赋值
let 声明的变量可以被重新赋值:
let name = '张三';
// ✅ 正确: 可以重新赋值
name = '李四';
console.log(name); // 李四
name = '王五';
console.log(name); // 王五
let count = 0;
count++;
count += 1;
console.log(count); // 21.5 块级作用域
let 和 const 都具有块级作用域,而 var 只有函数作用域:
1.5.1 let 和 const 的块级作用域
// 块级作用域示例
{
let name = '张三';
const age = 18;
console.log(name); // 张三
console.log(age); // 18
}
// ❌ 错误: 超出作用域,无法访问
console.log(name); // ReferenceError: name is not defined
console.log(age); // ReferenceError: age is not defined1.5.2 var 和 let/const 的区别
// var 只有函数作用域
function testVar() {
if (true) {
var name = '张三';
}
console.log(name); // ✅ 可以访问: 张三
}
testVar();
// let 和 const 有块级作用域
function testLet() {
if (true) {
let name = '李四';
const age = 18;
}
console.log(name); // ❌ 错误: ReferenceError
console.log(age); // ❌ 错误: ReferenceError
}
testLet();1.5.3 循环中的变量声明
// 使用 let: 每次循环都会创建新的变量
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
// 使用 var: 所有循环共享同一个变量
for (var j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j); // 3, 3, 3
}, 100);
}1.6 暂时性死区 (TDZ)
let 和 const 声明的变量存在暂时性死区(Temporal Dead Zone),在声明之前无法访问:
// ❌ 错误: 在声明前访问
console.log(name); // ReferenceError: Cannot access 'name' before initialization
const name = '张三';
// ❌ 错误: 在声明前访问
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 18;
// ✅ 正确: 声明后可以访问
console.log(name); // 张三1.6.1 暂时性死区的示例
function test() {
// 暂时性死区开始
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
// 暂时性死区结束
console.log(x); // ✅ 10
}
test();1.7 重复声明
let 和 const 不允许重复声明:
// ❌ 错误: 使用 let 重复声明
let name = '张三';
let name = '李四';
// SyntaxError: Identifier 'name' has already been declared
// ❌ 错误: 使用 const 重复声明
const age = 18;
const age = 19;
// SyntaxError: Identifier 'age' has already been declared
// ❌ 错误: let 和 const 也不能混合重复声明
let count = 0;
const count = 1;
// SyntaxError: Identifier 'count' has already been declared与 var 的对比:
// var 可以重复声明(但不推荐)
var name = '张三';
var name = '李四';
console.log(name); // 李四
// 但 let 和 const 不允许
let name = '张三';
let name = '李四';
// SyntaxError: Identifier 'name' has already been declared1.8 实际应用场景
1.8.1 何时使用 const
// 场景1: 声明常量
const PI = 3.141592653;
const MAX_SIZE = 100;
const API_URL = 'https://api.example.com';
// 场景2: 声明不会改变的配置
const config = {
theme: 'dark',
language: 'zh-CN',
timeout: 5000
};
// 场景3: 声明函数
function greet(name) {
console.log(`你好, ${name}`);
}
// 场景4: 声明不会重新赋值的对象
const user = {
id: 1,
name: '张三'
};
// 可以修改属性,但不能重新赋值整个对象
user.name = '李四'; // ✅
// user = {}; // ❌
// 场景5: 声明数组
const fruits = ['苹果', '香蕉', '橙子'];
fruits.push('葡萄'); // ✅ 可以修改数组内容
// fruits = []; // ❌ 不能重新赋值1.8.2 何时使用 let
// 场景1: 需要重新赋值的计数器
let count = 0;
count++;
count += 10;
// 场景2: 循环变量
for (let i = 0; i < 10; i++) {
console.log(i);
}
// 场景3: 需要改变值的变量
let score = 0;
score += 10;
score = 100;
// 场景4: 条件赋值
let result;
if (condition) {
result = 'yes';
} else {
result = 'no';
}
// 场景5: 累加计算
let sum = 0;
for (let num of numbers) {
sum += num;
}1.9 最佳实践
1.9.1 变量声明原则
// ✅ 默认使用 const
const name = '张三';
const age = 18;
const user = { id: 1, name: '张三' };
// ✅ 需要重新赋值时使用 let
let count = 0;
let score = 0;
let isValid = false;
// ❌ 避免使用 var (现代开发不推荐)
var name = '张三';1.9.2 命名规范
// 常量: 使用全大写
const MAX_SIZE = 100;
const API_URL = 'https://api.example.com';
const DEFAULT_CONFIG = {
theme: 'dark'
};
// 普通变量: 使用小驼峰
let userName = '张三';
let userId = 1;
let isLoggedIn = false;
// 布尔值: 使用 is/has/can 前缀
let isActive = true;
let hasPermission = false;
let canEdit = true;1.9.3 代码示例对比
// ❌ 不好的写法
var name = '张三';
var age = 18;
var isStudent = true;
// ✅ 好的写法: 默认使用 const
const name = '张三';
const age = 18;
const isStudent = true;
// ❌ 不好的写法: 不必要的 var
function process() {
var i = 0;
for (var i = 0; i < 10; i++) {
console.log(i);
}
}
// ✅ 好的写法: 使用 let
function process() {
for (let i = 0; i < 10; i++) {
console.log(i);
}
}
// ❌ 不好的写法: const 重新赋值
const count = 0;
count++; // 错误
// ✅ 好的写法: 使用 let
let count = 0;
count++; // 正确
// ❌ 不好的写法: let 用于不变的数据
let config = {
theme: 'dark',
language: 'zh-CN'
};
// ✅ 好的写法: 使用 const
const config = {
theme: 'dark',
language: 'zh-CN'
};
// 可以修改属性
config.theme = 'light'; // 正确1.10 总结对比表
| 特性 | let | const |
|---|---|---|
| 是否必须初始化 | ❌ 不必须 | ✅ 必须 |
| 是否可以重新赋值 | ✅ 可以 | ❌ 不可以 |
| 是否具有块级作用域 | ✅ 是 | ✅ 是 |
| 是否存在暂时性死区 | ✅ 是 | ✅ 是 |
| 是否可以重复声明 | ❌ 不可以 | ❌ 不可以 |
| 声明对象的属性可修改 | ✅ 可以 | ✅ 可以 |
| 声明数组的元素可修改 | ✅ 可以 | ✅ 可以 |
| 推荐使用场景 | 需要变化的变量 | 常量和不变的数据 |
1.11 常见问题
1.11.1 const 声明的对象可以修改吗?
答案: 可以修改对象的属性,但不能重新赋值整个对象。
const person = { name: '张三', age: 18 };
person.name = '李四'; // ✅ 可以
person.age = 19; // ✅ 可以
person = {}; // ❌ 不可以1.11.2 应该优先使用 let 还是 const?
答案: 优先使用 const,只有在需要重新赋值时才使用 let。
// ✅ 默认使用 const
const name = '张三';
const age = 18;
// 需要重新赋值时使用 let
let count = 0;
count++;1.11.3 什么时候使用 var?
答案: 现代开发中几乎不使用 var,统一使用 let 和 const。只有在维护旧代码时可能会遇到 var。
1.11.4 const 声明的数组可以修改吗?
答案: 可以修改数组的内容,但不能重新赋值整个数组。
const arr = [1, 2, 3];
arr.push(4); // ✅ 可以
arr[0] = 10; // ✅ 可以
arr = []; // ❌ 不可以1.11.5 为什么 const 更推荐使用?
答案:
- 代码更安全: 防止意外重新赋值
- 更易维护: 一旦声明就知道值不会改变
- 更好的性能: 引擎可以进行优化
- 更清晰的意图: 明确表示这是常量
2. Web API 基本认知
2.1 作用和分类
Web API 是浏览器提供的 API(应用程序编程接口),允许 JavaScript 与浏览器功能进行交互。
2.1.1 Web API 的作用
Web API 的主要作用包括:
- 操作网页内容: 通过 DOM API 动态修改网页的 HTML、CSS 和内容
- 响应用户交互: 处理点击、键盘输入、鼠标移动等事件
- 网络通信: 发送 HTTP 请求,与服务器进行数据交换
- 存储数据: 在浏览器中保存和读取数据
- 多媒体操作: 处理音频、视频、Canvas 绘图等
- 获取设备信息: 获取地理位置、设备方向、摄像头等信息
JavaScript 与 Web API 的关系:
JavaScript (编程语言)
↓ 调用
Web API (浏览器提供的接口)
↓ 访问
浏览器功能 (DOM、存储、网络等)2.1.2 Web API 的分类
分类方式一: 按功能分类
| API 类别 | 常用 API | 功能描述 |
|---|---|---|
| DOM API | document、querySelector、createElement | 操作网页文档结构 |
| BOM API | window、location、navigator、history | 操作浏览器窗口和导航 |
| 事件 API | addEventListener、removeEventListener | 处理用户交互和浏览器事件 |
| 网络 API | fetch、XMLHttpRequest、WebSocket | 发送网络请求 |
| 存储 API | localStorage、sessionStorage、IndexedDB | 在浏览器中存储数据 |
| 定时器 API | setTimeout、setInterval、requestAnimationFrame | 延迟和周期性执行任务 |
| Canvas API | getContext('2d')、getImageData | 2D/3D 图形绘制 |
| 地理 API | navigator.geolocation | 获取用户地理位置 |
| 多媒体 API | MediaRecorder、AudioContext | 处理音频、视频 |
| 通知 API | Notification、Service Worker | 显示通知和消息 |
分类方式二: W3C 标准分类
// 1. DOM (Document Object Model) - 文档对象模型
document.getElementById('id');
document.querySelector('.class');
// 2. BOM (Browser Object Model) - 浏览器对象模型
window.location.href;
window.navigator.userAgent;
window.history.back();
// 3. Web Storage - Web 存储
localStorage.setItem('key', 'value');
sessionStorage.getItem('key');
// 4. Web Workers - Web 工作线程
const worker = new Worker('worker.js');
// 5. Web Sockets - WebSocket 通信
const socket = new WebSocket('ws://example.com');2.1.3 Web API 的特点
// 特点1: 浏览器内置,无需额外安装
document.getElementById('app'); // 直接使用,无需导入
// 特点2: 由各大浏览器厂商实现(遵循 W3C 标准)
// Chrome、Firefox、Safari、Edge 都实现了标准 API
// 特点3: 可能存在兼容性问题
// 需要检查 API 是否支持
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(successCallback);
}
// 特点4: 异步 API 使用回调或 Promise
// Promise 形式
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// 回调形式
setTimeout(() => {
console.log('1秒后执行');
}, 1000);2.2 什么是 DOM
DOM (Document Object Model,文档对象模型) 是 HTML 和 XML 文档的编程接口。
2.2.1 DOM 的定义
DOM 将 HTML 文档表示为树形结构,每个节点都是对象,可以通过 JavaScript 访问和操作。
// HTML 文档
/*
<!DOCTYPE html>
<html>
<head>
<title>我的网页</title>
</head>
<body>
<div id="app">
<h1>欢迎</h1>
<p class="content">这是内容</p>
</div>
</body>
</html>
*/
// 通过 DOM 访问文档
console.log(document.title); // "我的网页"
console.log(document.body.innerHTML); // 获取 body 内容2.2.2 DOM 的作用
DOM 的主要作用:
- 访问页面元素: 查找、选择 HTML 元素
- 修改元素内容: 改变文本、HTML 内容
- 修改元素样式: 修改 CSS 样式
- 创建和删除元素: 动态添加或移除元素
- 响应事件: 处理用户的点击、输入等操作
示例:
// 1. 访问页面元素
const title = document.getElementById('app');
const paragraphs = document.querySelectorAll('p');
// 2. 修改元素内容
title.textContent = '新标题';
paragraphs[0].innerHTML = '<strong>加粗内容</strong>';
// 3. 修改元素样式
title.style.color = 'red';
title.style.fontSize = '24px';
// 4. 创建和添加元素
const newElement = document.createElement('div');
newElement.textContent = '新元素';
document.body.appendChild(newElement);
// 5. 响应事件
title.addEventListener('click', function() {
alert('被点击了!');
});2.2.3 DOM 的类型
// DOM Node 节点类型
const Node = {
ELEMENT_NODE: 1, // 元素节点
ATTRIBUTE_NODE: 2, // 属性节点
TEXT_NODE: 3, // 文本节点
COMMENT_NODE: 8, // 注释节点
DOCUMENT_NODE: 9, // 文档节点
DOCUMENT_FRAGMENT_NODE: 11 // 文档片段节点
};
// 检查节点类型
const element = document.getElementById('app');
console.log(element.nodeType === Node.ELEMENT_NODE); // true
const textNode = element.firstChild;
console.log(textNode.nodeType === Node.TEXT_NODE); // true2.3 DOM 树
DOM 将 HTML 文档表示为树形结构,称为 DOM 树。
2.3.1 DOM 树的结构


<!-- 示例 HTML -->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>DOM 树示例</title>
<style>
body { font-family: Arial; }
</style>
</head>
<body>
<div id="container">
<header>
<h1>网站标题</h1>
<nav>
<a href="#">首页</a>
<a href="#">关于</a>
</nav>
</header>
<main>
<section>
<h2>文章标题</h2>
<p>这是第一段内容</p>
<p>这是第二段内容</p>
</section>
<aside>
<h3>侧边栏</h3>
<ul>
<li>链接1</li>
<li>链接2</li>
</ul>
</aside>
</main>
<footer>
<p>© 2024 我的网站</p>
</footer>
</div>
</body>
</html>对应的 DOM 树结构:
Document (文档节点)
└── html (元素节点)
├── head (元素节点)
│ ├── meta (元素节点)
│ ├── title (元素节点)
│ │ └── "DOM 树示例" (文本节点)
│ └── style (元素节点)
│ └── "body { font-family: Arial; }" (文本节点)
└── body (元素节点)
└── div#container (元素节点)
├── header (元素节点)
│ ├── h1 (元素节点)
│ │ └── "网站标题" (文本节点)
│ └── nav (元素节点)
│ └── a (元素节点) × 2
│ └── "首页" / "关于" (文本节点)
├── main (元素节点)
│ ├── section (元素节点)
│ │ ├── h2 (元素节点)
│ │ │ └── "文章标题" (文本节点)
│ │ └── p (元素节点) × 2
│ │ └── "这是第一段内容" / "这是第二段内容" (文本节点)
│ └── aside (元素节点)
│ ├── h3 (元素节点)
│ │ └── "侧边栏" (文本节点)
│ └── ul (元素节点)
│ └── li (元素节点) × 2
│ └── "链接1" / "链接2" (文本节点)
└── footer (元素节点)
└── p (元素节点)
└── "© 2024 我的网站" (文本节点)2.3.2 DOM 节点关系
const parent = document.getElementById('parent');
const child = parent.querySelector('.child');
// 节点关系
console.log(parent.parentNode); // 父节点
console.log(parent.parentElement); // 父元素(不包括 Document)
console.log(parent.childNodes); // 所有子节点(包括文本节点)
console.log(parent.children); // 所有子元素(不包括文本节点)
console.log(parent.firstChild); // 第一个子节点
console.log(parent.firstElementChild); // 第一个子元素
console.log(parent.lastChild); // 最后一个子节点
console.log(parent.lastElementChild); // 最后一个子元素
console.log(child.previousSibling); // 上一个兄弟节点
console.log(child.previousElementSibling); // 上一个兄弟元素
console.log(child.nextSibling); // 下一个兄弟节点
console.log(child.nextElementSibling); // 下一个兄弟元素2.3.3 DOM 树的遍历
// 递归遍历 DOM 树
function traverseDOM(node, indent = 0) {
console.log(' '.repeat(indent) + node.nodeName);
// 遍历子节点
for (let i = 0; i < node.childNodes.length; i++) {
traverseDOM(node.childNodes[i], indent + 1);
}
}
traverseDOM(document.body);
// 获取所有文本节点
function getTextNodes(element) {
const textNodes = [];
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.textContent.trim()) {
textNodes.push(node);
}
}
return textNodes;
}
const texts = getTextNodes(document.body);
console.log(texts.map(t => t.textContent));2.3.4 DOM 树的操作
// 添加子节点
const parent = document.getElementById('parent');
const newChild = document.createElement('div');
newChild.textContent = '新子元素';
parent.appendChild(newChild); // 添加到末尾
parent.insertBefore(newChild, parent.firstChild); // 插入到指定位置
// 替换节点
const oldChild = parent.firstChild;
const replacement = document.createElement('span');
replacement.textContent = '替换的元素';
parent.replaceChild(replacement, oldChild);
// 删除节点
parent.removeChild(oldChild);
// 克隆节点
const cloned = parent.cloneNode(true); // true 表示深度克隆(包括子节点)
const shallowClone = parent.cloneNode(false); // false 表示浅克隆(不包括子节点)2.4 DOM 对象
DOM 提供了多个对象来操作文档的不同部分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>DOM树</div>
<script>
//div标签会被转换为JS对象(Object)
const divDom = document.querySelector('div')
console.dir(divDom)
console.log(typeof(divDom))
</script>
</body>
</html>
2.4.1 document 对象
document 对象代表整个 HTML 文档,是 DOM 树的根节点。
常用属性:
// 文档信息
document.title; // 页面标题
document.URL; // 页面 URL
document.domain; // 域名
document.referrer; // 来源 URL
document.lastModified; // 最后修改时间
// 文档类型
document.doctype; // 文档类型声明
document.documentElement; // html 元素
document.body; // body 元素
document.head; // head 元素
// 集合
document.all; // 所有元素(不推荐)
document.forms; // 所有表单
document.images; // 所有图片
document.links; // 所有链接
document.scripts; // 所有脚本
// 准备状态
document.readyState; // loading、interactive、complete常用方法:
// 查找元素
document.getElementById('id'); // 通过 ID 查找
document.getElementsByClassName('class'); // 通过类名查找
document.getElementsByName('name'); // 通过 name 属性查找
document.getElementsByTagName('tag'); // 通过标签名查找
document.querySelector('selector'); // 查找第一个匹配元素
document.querySelectorAll('selector'); // 查找所有匹配元素
// 创建元素
document.createElement('div'); // 创建元素
document.createTextNode('text'); // 创建文本节点
document.createDocumentFragment(); // 创建文档片段
document.createAttribute('class'); // 创建属性节点
document.createComment('comment'); // 创建注释节点
// 其他方法
document.write('content'); // 写入内容(不推荐)
document.open(); // 打开文档流
document.close(); // 关闭文档流示例:
// 查找元素
const app = document.getElementById('app');
const buttons = document.getElementsByClassName('btn');
const allDivs = document.querySelectorAll('div');
// 创建并添加元素
const newDiv = document.createElement('div');
newDiv.className = 'new-element';
newDiv.textContent = '这是新创建的元素';
document.body.appendChild(newDiv);
// 文档加载完成
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM 加载完成');
});
// 或使用
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
console.log('文档完全加载');
}
});2.4.2 Element 对象
Element 对象代表 HTML 元素,是所有 HTML 元素的基类。
常用属性:
const element = document.getElementById('app');
// 标签和属性
element.tagName; // 标签名(大写,如 DIV)
element.id; // ID 属性
element.className; // class 属性
element.classList; // 类名列表(对象)
// 样式
element.style; // 行内样式对象
element.style.color; // 颜色
element.style.display; // 显示方式
// 内容
element.innerHTML; // HTML 内容(可以解析 HTML)
element.textContent; // 纯文本内容(不解析 HTML)
element.innerText; // 类似 textContent
// 属性
element.attributes; // 所有属性
element.getAttribute('data-id'); // 获取属性
element.setAttribute('data-id', '123'); // 设置属性
element.removeAttribute('data-id'); // 删除属性
// 位置和尺寸
element.offsetWidth; // 元素宽度(包括边框和内边距)
element.offsetHeight; // 元素高度(包括边框和内边距)
element.clientWidth; // 元素宽度(不包括边框)
element.clientHeight; // 元素高度(不包括边框)
element.offsetLeft; // 相对父元素左边距
element.offsetTop; // 相对父元素上边距
// 类名操作
element.classList.add('class1', 'class2'); // 添加类
element.classList.remove('class1'); // 删除类
element.classList.toggle('active'); // 切换类
element.classList.contains('active'); // 检查类
element.classList.replace('old', 'new'); // 替换类示例:
// 操作类名
const button = document.getElementById('submit-btn');
button.classList.add('btn', 'btn-primary');
button.classList.contains('btn-primary'); // true
button.classList.toggle('active'); // 切换 active 类
button.classList.remove('btn-primary');
// 操作属性
const link = document.querySelector('a');
link.getAttribute('href'); // 获取 href
link.setAttribute('target', '_blank'); // 设置 target
link.dataset.userId = '123'; // 设置 data-* 属性
// 操作内容
const container = document.getElementById('content');
container.innerHTML = '<p><strong>HTML 内容</strong></p>';
container.textContent = '纯文本内容';2.4.3 NodeList 对象
NodeList 是节点的集合,类似数组但不是真正的数组。
// querySelectorAll 返回 NodeList
const divs = document.querySelectorAll('div');
console.log(divs); // NodeList [div, div, div, ...]
// NodeList 的特点
console.log(divs.length); // 长度
console.log(divs[0]); // 索引访问
console.log(divs.item(0)); // item() 方法
// NodeList 不是数组,没有数组方法
// divs.push() - 不存在
// 转换为数组
const divsArray = Array.from(divs);
const divsArray2 = [...divs];
divsArray.forEach(div => console.log(div));
// forEach 方法(现代浏览器支持)
divs.forEach((div, index) => {
console.log(`索引 ${index}:`, div);
});2.4.4 HTMLCollection 对象
HTMLCollection 是 HTML 元素的集合,也是类数组对象。
// getElementsByClassName 返回 HTMLCollection
const buttons = document.getElementsByClassName('btn');
console.log(buttons); // HTMLCollection [button, button, ...]
// HTMLCollection 的特点
console.log(buttons.length); // 长度
console.log(buttons[0]); // 索引访问
console.log(buttons.item(0)); // item() 方法
console.log(buttons.namedItem('name')); // 通过 name 或 id 访问
// HTMLCollection 是动态的(实时更新)
const container = document.getElementById('container');
const items = container.getElementsByClassName('item');
console.log(items.length); // 例如: 3
// 添加新元素
const newItem = document.createElement('div');
newItem.className = 'item';
container.appendChild(newItem);
console.log(items.length); // 4 (自动更新!)
// 转换为数组(避免动态性)
const itemsArray = Array.from(items);
const itemsArray2 = [...items];2.4.5 DOM 操作最佳实践
// 最佳实践1: 批量操作时使用文档片段
function addMultipleItems(container, items) {
// 不好的做法: 每次添加都触发重排
// items.forEach(item => container.appendChild(item));
// 好的做法: 使用文档片段
const fragment = document.createDocumentFragment();
items.forEach(item => fragment.appendChild(item));
container.appendChild(fragment); // 只触发一次重排
}
// 最佳实践2: 缓存 DOM 查询结果
// 不好的做法: 每次都查询
for (let i = 0; i < 10; i++) {
const element = document.getElementById('app');
element.style.opacity = i / 10;
}
// 好的做法: 缓存查询结果
const element = document.getElementById('app');
for (let i = 0; i < 10; i++) {
element.style.opacity = i / 10;
}
// 最佳实践3: 优先使用 querySelector/querySelectorAll
// 推荐: 选择器语法强大,一致性好
const element = document.querySelector('.class');
const elements = document.querySelectorAll('div.class');
// 其次: 旧的 API 也可用,但功能有限
const element2 = document.getElementById('id');
const elements2 = document.getElementsByClassName('class');
// 最佳实践4: 使用 classList 操作类名
// 推荐: 更高效、更易读
element.classList.add('active');
element.classList.remove('hidden');
element.classList.toggle('active');
// 不推荐: 直接操作 className 字符串
// element.className += ' active hidden';
// 最佳实践5: 使用 dataset 操作 data-* 属性
// 推荐
const userId = element.dataset.userId;
element.dataset.userName = '张三';
// 不推荐
const userId2 = element.getAttribute('data-user-id');
element.setAttribute('data-user-name', '张三');2.4.6 DOM 操作示例
示例1: 动态创建列表
function createList(items) {
const ul = document.createElement('ul');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
li.className = 'list-item';
li.dataset.index = items.indexOf(item);
fragment.appendChild(li);
});
ul.appendChild(fragment);
ul.className = 'item-list';
return ul;
}
const items = ['苹果', '香蕉', '橙子', '葡萄'];
const list = createList(items);
document.getElementById('app').appendChild(list);示例2: 表单验证
function validateForm(formId) {
const form = document.getElementById(formId);
const inputs = form.querySelectorAll('input, textarea, select');
let isValid = true;
const errors = [];
inputs.forEach(input => {
const name = input.name;
const value = input.value.trim();
const required = input.hasAttribute('required');
// 移除旧的错误提示
const existingError = form.querySelector(`[data-error="${name}"]`);
if (existingError) {
existingError.remove();
}
// 验证必填字段
if (required && !value) {
isValid = false;
errors.push(`${name} 不能为空`);
showError(input, `${name} 不能为空`);
return;
}
// 验证邮箱
if (name === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
isValid = false;
errors.push('邮箱格式不正确');
showError(input, '邮箱格式不正确');
}
}
});
return { isValid, errors };
}
function showError(input, message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.dataset.error = input.name;
errorDiv.textContent = message;
errorDiv.style.color = 'red';
errorDiv.style.fontSize = '12px';
input.parentNode.appendChild(errorDiv);
}示例3: 图片懒加载
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
}
// 页面加载时启动懒加载
document.addEventListener('DOMContentLoaded', lazyLoadImages);3. DOM 元素的获取
3.1 根据 CSS 选择器来获取 DOM 元素
3.1.1 querySelector 方法
querySelector() 方法返回文档中匹配指定 CSS 选择器的第一个元素。
基本语法:
document.querySelector(selector)
element.querySelector(selector)常用示例:
// 通过 ID 获取
const element = document.querySelector('#myId');
// 通过类名获取
const element = document.querySelector('.myClass');
// 通过标签名获取
const element = document.querySelector('div');
// 通过属性获取
const element = document.querySelector('[data-id="123"]');
// 组合选择器
const element = document.querySelector('div.active');
const element = document.querySelector('ul > li:first-child');
const element = document.querySelector('.class1.class2');
const element = document.querySelector('#app .container > p');在特定元素内查找:
const container = document.getElementById('container');
// 只在 container 内部查找
const firstParagraph = container.querySelector('p');
const activeButton = container.querySelector('.btn.active');注意事项:
// 如果找不到元素,返回 null
const notFound = document.querySelector('.non-existent');
console.log(notFound); // null
// 使用前最好检查是否存在
const element = document.querySelector('#myId');
if (element) {
// 元素存在,可以操作
element.textContent = 'Hello';
}
// 或使用可选链操作符(现代浏览器)
document.querySelector('#myId')?.textContent = 'Hello';3.1.2 querySelectorAll 方法
querySelectorAll() 方法返回文档中匹配指定 CSS 选择器的所有元素,返回一个 NodeList。
基本语法:
document.querySelectorAll(selector)
element.querySelectorAll(selector)常用示例:
// 获取所有 div 元素
const divs = document.querySelectorAll('div');
console.log(divs.length); // div 元素的数量
// 获取所有具有特定类名的元素
const buttons = document.querySelectorAll('.btn');
console.log(buttons); // NodeList [button, button, button, ...]
// 获取所有具有特定属性的元素
const links = document.querySelectorAll('a[target="_blank"]');
// 复杂选择器
const items = document.querySelectorAll('.list > li.item');
const checked = document.querySelectorAll('input[type="checkbox"]:checked');
// 伪类选择器
const firstItem = document.querySelectorAll('li:first-child');
const evenItems = document.querySelectorAll('tr:nth-child(even)');
const visibleItems = document.querySelectorAll('.item:not(.hidden)');在特定元素内查找:
const container = document.getElementById('container');
// 只在 container 内部查找
const paragraphs = container.querySelectorAll('p');
console.log(paragraphs.length); // container 内的 p 元素数量遍历 NodeList:
// 方式1: 使用 forEach (现代浏览器支持)
const items = document.querySelectorAll('.item');
items.forEach((item, index) => {
console.log(`索引 ${index}:`, item.textContent);
});
// 方式2: 转换为数组
const items = document.querySelectorAll('.item');
const itemsArray = Array.from(items);
itemsArray.map(item => item.textContent);
// 方式3: 使用展开运算符
const itemsArray = [...document.querySelectorAll('.item')];
// 方式4: 传统 for 循环
const items = document.querySelectorAll('.item');
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
// 方式5: for...of 循环
for (const item of items) {
console.log(item);
}NodeList 的特点:
const items = document.querySelectorAll('.item');
// NodeList 是类数组对象
console.log(items.length); // 元素数量
console.log(items[0]); // 通过索引访问
console.log(items.item(0)); // 通过 item() 方法访问
// NodeList 的两种类型
// 1. 静态 NodeList (querySelectorAll 返回)
// - 不会随 DOM 变化而自动更新
const staticList = document.querySelectorAll('.item');
console.log(staticList.length); // 假设 5
// 添加新元素
document.querySelector('.container').appendChild(newItem);
console.log(staticList.length); // 仍然是 5
// 2. 动态 NodeList (某些旧方法返回)
// - 会随 DOM 变化而自动更新
// querySelectorAll 返回的是静态 NodeList3.2 其他获取 DOM 元素方法
3.2.1 getElementById 方法
通过元素的 ID 属性获取元素,返回单个元素。
语法:
document.getElementById(id)示例:
// 基本用法
const element = document.getElementById('myId');
// 如果找不到返回 null
const notFound = document.getElementById('non-existent');
console.log(notFound); // null
// 使用示例
const header = document.getElementById('header');
header.style.backgroundColor = 'blue';
header.textContent = 'New Header';注意事项:
// ID 在文档中应该是唯一的
// 如果有多个相同 ID,只返回第一个
<div id="app">第一个</div>
<div id="app">第二个</div>
const element = document.getElementById('app');
// 只返回第一个元素
// getElementById 只能在 document 对象上调用
// 不能在其他元素上调用
const container = document.getElementById('container');
// container.getElementById('item'); // ❌ 错误!
// 性能最优的获取元素方法
// 优先使用 getElementById 而不是 querySelector('#id')3.2.2 getElementsByClassName 方法
通过类名获取元素集合,返回 HTMLCollection(动态集合)。
语法:
document.getElementsByClassName(className)
element.getElementsByClassName(className)示例:
// 获取所有具有指定类名的元素
const buttons = document.getElementsByClassName('btn');
console.log(buttons.length);
// 在特定元素内查找
const container = document.getElementById('container');
const items = container.getElementsByClassName('item');
// 获取具有多个类名的元素
const activeButtons = document.getElementsByClassName('btn active');
// 遍历 HTMLCollection
const elements = document.getElementsByClassName('item');
for (let i = 0; i < elements.length; i++) {
console.log(elements[i]);
}
// 或转换为数组
const elementsArray = Array.from(elements);
elementsArray.forEach(el => console.log(el));HTMLCollection 的特点:
const container = document.getElementById('container');
const items = container.getElementsByClassName('item');
console.log(items.length); // 假设 3
// HTMLCollection 是动态的,会自动更新 DOM 变化
const newItem = document.createElement('div');
newItem.className = 'item';
container.appendChild(newItem);
console.log(items.length); // 4 (自动更新!)
// 遍历时注意动态性
const items = document.getElementsByClassName('item');
for (let i = 0; i < items.length; i++) {
// 如果在循环中修改 DOM,可能导致问题
items[i].remove();
// items[i] 可能会跳过某些元素或重复处理
}
// 安全的做法: 转换为数组
const items = [...document.getElementsByClassName('item')];
items.forEach(item => item.remove());3.2.3 getElementsByTagName 方法
通过标签名获取元素集合,返回 HTMLCollection(动态集合)。
语法:
document.getElementsByTagName(tagName)
element.getElementsByTagName(tagName)示例:
// 获取所有 div 元素
const divs = document.getElementsByTagName('div');
console.log(divs.length);
// 获取所有段落
const paragraphs = document.getElementsByTagName('p');
// 获取所有链接
const links = document.getElementsByTagName('a');
// 在特定元素内查找
const container = document.getElementById('container');
const images = container.getElementsByTagName('img');
// 遍历
for (let i = 0; i < divs.length; i++) {
console.log(divs[i].textContent);
}特殊用法:
// 使用 '*' 获取所有元素
const allElements = document.getElementsByTagName('*');
console.log(allElements.length); // 文档中所有元素的数量
// 获取特定类型的所有元素
const allInputs = document.getElementsByTagName('input');
const allButtons = document.getElementsByTagName('button');3.2.4 getElementsByName 方法
通过 name 属性获取元素集合,返回 NodeList(动态集合)。
语法:
document.getElementsByName(name)示例:
// HTML: <input name="color" type="radio" value="red">
// <input name="color" type="radio" value="blue">
// <input name="color" type="radio" value="green">
const radios = document.getElementsByName('color');
console.log(radios.length); // 3
// 获取选中的单选按钮
let selectedColor;
for (const radio of radios) {
if (radio.checked) {
selectedColor = radio.value;
break;
}
}
console.log(selectedColor); // 'red' 或 'blue' 或 'green'典型应用场景:
// 单选按钮组
const genders = document.getElementsByName('gender');
const gender = Array.from(genders).find(g => g.checked)?.value;
// 复选框组
const hobbies = document.getElementsByName('hobby');
const selectedHobbies = Array.from(hobbies)
.filter(h => h.checked)
.map(h => h.value);
// 表单验证
function validateForm() {
const radios = document.getElementsByName('options');
let checked = false;
for (const radio of radios) {
if (radio.checked) {
checked = true;
break;
}
}
if (!checked) {
alert('请选择一个选项');
return false;
}
return true;
}3.2.5 获取表单元素的特殊方法
forms 集合:
// 获取所有表单
const forms = document.forms;
console.log(forms.length);
// 通过索引访问
const firstForm = document.forms[0];
// 通过 name 属性访问
const loginForm = document.forms['login-form'];
const registerForm = document.forms.registerForm;form.elements 集合:
const form = document.getElementById('myForm');
// 获取表单内的所有表单控件
const elements = form.elements;
console.log(elements.length);
// 通过 name 属性访问
const username = form.elements['username'];
const password = form.elements.password;
// 遍历表单元素
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
console.log(element.name, element.value);
}其他表单集合:
// 获取所有图片
const images = document.images;
// 获取所有链接
const links = document.links;
// 获取所有脚本
const scripts = document.scripts;
// 获取所有锚点(带 name 属性的 a 标签)
const anchors = document.anchors;3.2.6 各种获取方法的对比
| 方法 | 返回类型 | 是否动态 | 选择器支持 | 性能 |
|---|---|---|---|---|
| getElementById | 单个元素 | - | 仅 ID | ⭐⭐⭐⭐⭐ 最快 |
| querySelector | 单个元素 | 否 | CSS 选择器 | ⭐⭐⭐ 较快 |
| getElementsByClassName | HTMLCollection | 是 | 仅类名 | ⭐⭐⭐⭐ 快 |
| getElementsByTagName | HTMLCollection | 是 | 仅标签名 | ⭐⭐⭐⭐ 快 |
| getElementsByName | NodeList | 是 | name 属性 | ⭐⭐⭐ 较快 |
| querySelectorAll | NodeList | 否 | CSS 选择器 | ⭐⭐ 较慢 |
使用建议:
// ✅ 推荐用法
// 1. 使用 ID 获取单个元素
const element = document.getElementById('myId');
// 2. 使用 querySelector 获取单个元素(复杂选择器)
const element = document.querySelector('.container .item.active');
// 3. 使用 querySelectorAll 获取多个元素
const items = document.querySelectorAll('.item');
// 4. 性能敏感场景使用旧的 API
const buttons = document.getElementsByClassName('btn');
const divs = document.getElementsByTagName('div');
// ❌ 不推荐用法
// 1. 不要用 querySelector 替代 getElementById
const element = document.querySelector('#myId'); // 性能较差
// 2. 不要滥用 querySelectorAll
const items = document.querySelectorAll('div'); // 如果只需要标签名,用 getElementsByTagName 更快3.2.7 实际应用示例
示例1: 获取表单数据
function getFormData(formId) {
const form = document.getElementById(formId);
const data = {};
// 使用 querySelectorAll 获取所有输入字段
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (input.type === 'checkbox') {
data[input.name] = input.checked;
} else if (input.type === 'radio') {
if (input.checked) {
data[input.name] = input.value;
}
} else {
data[input.name] = input.value;
}
});
return data;
}
const formData = getFormData('login-form');
console.log(formData);示例2: 批量操作元素
// 给所有按钮添加点击事件
const buttons = document.querySelectorAll('.btn');
buttons.forEach(button => {
button.addEventListener('click', function() {
console.log('按钮被点击');
});
});
// 给所有输入框添加焦点事件
const inputs = document.querySelectorAll('input[type="text"]');
inputs.forEach(input => {
input.addEventListener('focus', function() {
this.classList.add('focused');
});
input.addEventListener('blur', function() {
this.classList.remove('focused');
});
});示例3: 表格操作
function getTableData(tableId) {
const table = document.getElementById(tableId);
const rows = table.querySelectorAll('tbody tr');
const data = [];
rows.forEach(row => {
const cells = row.querySelectorAll('td');
const rowData = Array.from(cells).map(cell => cell.textContent);
data.push(rowData);
});
return data;
}
function highlightTable(tableId, rowIndex) {
const table = document.getElementById(tableId);
const rows = table.querySelectorAll('tbody tr');
rows.forEach((row, index) => {
if (index === rowIndex) {
row.classList.add('highlighted');
} else {
row.classList.remove('highlighted');
}
});
}示例4: 图片懒加载检测
function checkLazyImages() {
// 获取所有带有 data-src 属性的图片
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
const rect = img.getBoundingClientRect();
// 检查图片是否在视口中
if (rect.top < window.innerHeight && rect.bottom >= 0) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
img.classList.add('loaded');
}
});
}
// 监听滚动事件
window.addEventListener('scroll', checkLazyImages);
window.addEventListener('load', checkLazyImages);示例5: 表单验证
function validateForm(formId) {
const form = document.getElementById(formId);
const requiredInputs = form.querySelectorAll('[required]');
let isValid = true;
requiredInputs.forEach(input => {
const value = input.value.trim();
if (!value) {
isValid = false;
input.classList.add('error');
// 创建错误提示
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = '此字段不能为空';
input.parentNode.appendChild(errorDiv);
} else {
input.classList.remove('error');
const existingError = input.parentNode.querySelector('.error-message');
if (existingError) {
existingError.remove();
}
}
});
return isValid;
}
// 绑定提交事件
document.getElementById('myForm').addEventListener('submit', function(e) {
if (!validateForm('myForm')) {
e.preventDefault(); // 阻止表单提交
}
});示例6: 导航菜单高亮
function highlightActiveMenu() {
// 获取当前页面的路径
const currentPath = window.location.pathname;
// 获取所有导航链接
const navLinks = document.querySelectorAll('.nav-menu a');
navLinks.forEach(link => {
const href = link.getAttribute('href');
// 检查链接是否匹配当前路径
if (currentPath.includes(href)) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
// 页面加载时执行
document.addEventListener('DOMContentLoaded', highlightActiveMenu);3.3 总结对比表
| 获取方法 | 返回类型 | 选择器类型 | 是否动态 | 推荐场景 |
|---|---|---|---|---|
| getElementById | 单个元素 | ID | - | 获取单个元素(最快) |
| querySelector | 单个元素 | CSS 选择器 | 否 | 复杂选择器获取单个元素 |
| querySelectorAll | NodeList | CSS 选择器 | 否 | 复杂选择器获取多个元素 |
| getElementsByClassName | HTMLCollection | 类名 | 是 | 按类名获取元素 |
| getElementsByTagName | HTMLCollection | 标签名 | 是 | 按标签名获取元素 |
| getElementsByName | NodeList | name 属性 | 是 | 按表单 name 获取元素 |
3.4 最佳实践
3.4.1 性能优化
// ✅ 缓存 DOM 查询结果
const header = document.getElementById('header');
const sidebar = document.querySelector('.sidebar');
const buttons = document.querySelectorAll('.btn');
// ❌ 避免重复查询
function badExample() {
document.getElementById('header').style.color = 'red';
document.getElementById('header').style.fontSize = '20px';
document.getElementById('header').style.fontWeight = 'bold';
}
// ✅ 好的做法
function goodExample() {
const header = document.getElementById('header');
header.style.color = 'red';
header.style.fontSize = '20px';
header.style.fontWeight = 'bold';
}3.4.2 选择器优化
// ✅ 使用更具体的选择器
const element = document.querySelector('#app .container .item');
// ❌ 避免过于复杂的选择器
const element = document.querySelector('body > div#app > div.container > div.item.active');
// ✅ 使用 ID 选择器
const element = document.getElementById('myId');
// ❌ 不要用 querySelector 替代 getElementById
const element = document.querySelector('#myId');3.4.3 错误处理
// ✅ 总是检查元素是否存在
const element = document.getElementById('myId');
if (element) {
element.textContent = 'Hello';
}
// ✅ 使用可选链操作符(现代浏览器)
document.getElementById('myId')?.textContent = 'Hello';
document.querySelector('.item')?.classList.add('active');
// ✅ 提供默认值
const element = document.querySelector('.item') || createDefaultElement();4. 操作元素内容
4.1 innerText 属性
innerText 属性用于获取或设置元素的文本内容,会保留格式和样式。
4.1.1 获取文本内容
基本用法:
const element = document.getElementById('content');
// 获取文本内容
const text = element.innerText;
console.log(text);特点:
<div id="content">
<h1>标题</h1>
<p>这是一段<strong>加粗</strong>文本</p>
<p style="display: none;">这是隐藏的文本</p>
</div>const element = document.getElementById('content');
const text = element.innerText;
console.log(text);
/*
输出:
标题
这是一段加粗文本
*/innerText 的特点:
- ✅ 保留文本格式(换行、空格等)
- ✅ 会触发重排(reflow),性能较低
- ✅ 不返回隐藏元素的文本(display: none)
- ✅ 类似用户看到的内容
- ❌ 不是标准属性(被 innerText 替代)
4.1.2 设置文本内容
基本用法:
const element = document.getElementById('title');
// 设置文本内容
element.innerText = '新标题';
// 清空内容
element.innerText = '';示例:
const greeting = document.getElementById('greeting');
const name = '张三';
const age = 18;
// 设置文本
greeting.innerText = `你好,${name}!今年${age}岁。`;
// 动态更新
function updateTime() {
const timeElement = document.getElementById('time');
const now = new Date();
timeElement.innerText = `当前时间: ${now.toLocaleTimeString()}`;
}
setInterval(updateTime, 1000);4.1.3 innerText 与样式的关系
const element = document.getElementById('content');
// 修改元素的样式会影响 innerText 的输出
element.style.display = 'none';
console.log(element.innerText); // '' (元素隐藏时返回空字符串)
element.style.display = 'block';
console.log(element.innerText); // 恢复文本内容4.2 textContent 属性
textContent 属性用于获取或设置元素的文本内容,包括所有后代节点的文本。
4.2.1 获取文本内容
基本用法:
const element = document.getElementById('content');
// 获取所有文本内容
const text = element.textContent;
console.log(text);特点:
<div id="content">
<h1>标题</h1>
<p>这是一段<strong>加粗</strong>文本</p>
<p style="display: none;">这是隐藏的文本</p>
</div>const element = document.getElementById('content');
const text = element.textContent;
console.log(text);
/*
输出:
标题
这是一段加粗文本
这是隐藏的文本
*/textContent 的特点:
- ✅ 返回所有文本,包括隐藏元素
- ✅ 不保留格式,所有文本合并
- ✅ 不会触发重排,性能较高
- ✅ 标准属性,推荐使用
- ❌ 不考虑 CSS 样式影响
4.2.2 设置文本内容
基本用法:
const element = document.getElementById('title');
// 设置文本内容
element.textContent = '新标题';
// 清空内容
element.textContent = '';示例:
const card = document.createElement('div');
card.innerHTML = `
<h3>卡片标题</h3>
<p>这是一段描述</p>
`;
// 使用 textContent 替换所有内容
card.textContent = '新内容';
console.log(card.innerHTML); // '新内容' (所有子元素被移除)4.2.3 textContent 性能优势
// ✅ textContent 性能更好
const largeElement = document.getElementById('large-content');
largeElement.textContent = '新的文本内容';
// ❌ innerText 会触发重排,性能较差
const largeElement = document.getElementById('large-content');
largeElement.innerText = '新的文本内容';4.3 innerHTML 属性
innerHTML 属性用于获取或设置元素的 HTML 内容。
4.3.1 获取 HTML 内容
基本用法:
const element = document.getElementById('content');
// 获取 HTML 内容
const html = element.innerHTML;
console.log(html);示例:
<div id="content">
<h1>标题</h1>
<p>段落文本</p>
</div>const element = document.getElementById('content');
const html = element.innerHTML;
console.log(html);
/*
输出:
<h1>标题</h1>
<p>段落文本</p>
*/4.3.2 设置 HTML 内容
基本用法:
const element = document.getElementById('content');
// 设置 HTML 内容
element.innerHTML = '<h2>新标题</h2><p>新段落</p>';
// 清空内容
element.innerHTML = '';动态创建 HTML:
const container = document.getElementById('container');
// 创建列表
const items = ['苹果', '香蕉', '橙子'];
let html = '<ul>';
items.forEach(item => {
html += `<li>${item}</li>`;
});
html += '</ul>';
container.innerHTML = html;
// 更简洁的写法
container.innerHTML = `
<ul>
${items.map(item => `<li>${item}</li>`).join('')}
</ul>
`;4.3.3 模板字符串使用
const user = {
name: '张三',
age: 18,
email: 'zhangsan@example.com'
};
const cardHTML = `
<div class="user-card">
<h3>${user.name}</h3>
<p>年龄: ${user.age}</p>
<p>邮箱: ${user.email}</p>
<button class="btn">关注</button>
</div>
`;
document.getElementById('app').innerHTML = cardHTML;4.3.4 安全性注意事项
⚠️ XSS 攻击风险:
// ❌ 危险: 直接插入用户输入
const userInput = '<script>alert("XSS攻击")</script>';
document.getElementById('content').innerHTML = userInput;
// 脚本会被执行!
// ❌ 危险: 使用事件处理器
const userInput = '<img src=x onerror="alert(1)">';
document.getElementById('content').innerHTML = userInput;
// 会触发 onerror 事件!✅ 安全的做法:
// 方法1: 使用 textContent (推荐)
const userInput = '<script>alert("XSS")</script>';
document.getElementById('content').textContent = userInput;
// 输出: <script>alert("XSS")</script> (纯文本)
// 方法2: 转义 HTML
function escapeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
const userInput = '<script>alert("XSS")</script>';
document.getElementById('content').innerHTML = escapeHTML(userInput);
// 方法3: 使用 DOM API 创建元素
const userInput = '用户输入的文本';
const p = document.createElement('p');
p.textContent = userInput;
document.getElementById('content').appendChild(p);4.4 outerHTML 属性
outerHTML 属性用于获取或设置元素及其子元素的完整 HTML。
4.4.1 获取完整的 HTML
<div id="container">
<h1>标题</h1>
<p>内容</p>
</div>const element = document.getElementById('container');
// 获取包括元素本身的 HTML
const html = element.outerHTML;
console.log(html);
/*
输出:
<div id="container">
<h1>标题</h1>
<p>内容</p>
</div>
*/4.4.2 替换元素
const oldElement = document.getElementById('old-element');
// 替换元素及其内容
oldElement.outerHTML = '<div id="new-element">新元素</div>';
// 旧元素已从 DOM 中移除
console.log(oldElement.parentElement); // null示例:
// 动态更新元素
function updateCard(id, newData) {
const card = document.getElementById(id);
if (card) {
card.outerHTML = `
<div id="${id}" class="card">
<h3>${newData.title}</h3>
<p>${newData.description}</p>
<span class="date">${newData.date}</span>
</div>
`;
}
}
updateCard('card-1', {
title: '新标题',
description: '新描述',
date: '2024-01-01'
});4.5 三种属性对比
| 属性 | 用途 | 是否解析 HTML | 是否触发重排 | 返回隐藏元素 | 性能 |
|---|---|---|---|---|---|
| innerText | 获取/设置文本 | ❌ | ✅ | ❌ | ⭐⭐ 较低 |
| textContent | 获取/设置文本 | ❌ | ❌ | ✅ | ⭐⭐⭐⭐⭐ 高 |
| innerHTML | 获取/设置 HTML | ✅ | ✅ | ✅ | ⭐⭐⭐ 中等 |
| outerHTML | 获取/设置完整 HTML | ✅ | ✅ | ✅ | ⭐⭐⭐ 中等 |
对比示例:
<div id="content" style="display: none;">
<h1>标题</h1>
<p>段落 <strong>加粗</strong> 文本</p>
</div>const element = document.getElementById('content');
console.log(element.innerText); // '' (元素隐藏)
console.log(element.textContent); // '\n 标题\n 段落 加粗 文本\n'
console.log(element.innerHTML); // '\n <h1>标题</h1>\n <p>段落 <strong>加粗</strong> 文本</p>\n'
console.log(element.outerHTML); // '<div id="content" style="display: none;">\n <h1>标题</h1>\n <p>段落 <strong>加粗</strong> 文本</p>\n</div>'4.6 使用建议
4.6.1 选择合适的属性
// ✅ 场景1: 只需要文本内容,使用 textContent (性能最好)
const element = document.getElementById('content');
const text = element.textContent;
// ✅ 场景2: 需要保留格式,使用 innerText
const element = document.getElementById('content');
const text = element.innerText;
// ✅ 场景3: 需要 HTML 结构,使用 innerHTML
const element = document.getElementById('content');
const html = element.innerHTML;
// ❌ 避免用 textContent 获取格式化的文本
const element = document.getElementById('content');
const text = element.textContent; // 格式会丢失4.6.2 性能优化
// ✅ 批量操作时使用文档片段
function addMultipleItems(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
fragment.appendChild(div);
});
document.getElementById('container').appendChild(fragment);
}
// ❌ 避免频繁操作 DOM
function badExample() {
items.forEach(item => {
const container = document.getElementById('container');
container.innerHTML += `<div>${item}</div>`; // 每次都重新渲染
});
}4.6.3 安全使用 innerHTML
// ✅ 安全的模板函数
function safeHTML(strings, ...values) {
const escaped = values.map(value => {
if (typeof value !== 'string') return value;
return value.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
});
return strings.reduce((result, string, i) => {
return result + string + (escaped[i] || '');
}, '');
}
const name = '<script>alert(1)</script>';
const html = safeHTML`<div>${name}</div>`;
document.getElementById('app').innerHTML = html;
// 安全: 输出 <div><script>alert(1)</script></div>4.7 实际应用示例
4.7.1 动态生成列表
const fruits = ['苹果', '香蕉', '橙子', '葡萄', '草莓'];
function createList(items) {
const list = document.getElementById('fruit-list');
// 方法1: 使用 innerHTML
list.innerHTML = `
<ul>
${items.map(item => `<li>${item}</li>`).join('')}
</ul>
`;
// 方法2: 使用 DOM API (更安全)
list.innerHTML = '';
const ul = document.createElement('ul');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
ul.appendChild(li);
});
list.appendChild(ul);
}
createList(fruits);4.7.2 数据渲染
const users = [
{ id: 1, name: '张三', age: 18 },
{ id: 2, name: '李四', age: 20 },
{ id: 3, name: '王五', age: 22 }
];
function renderUsers(users) {
const container = document.getElementById('user-list');
container.innerHTML = `
<table class="user-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
</tr>
</thead>
<tbody>
${users.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
renderUsers(users);4.7.3 富文本编辑器
// 获取编辑器内容
function getEditorContent() {
const editor = document.getElementById('editor');
return editor.innerHTML;
}
// 设置编辑器内容
function setEditorContent(html) {
const editor = document.getElementById('editor');
editor.innerHTML = html;
}
// 插入文本
function insertText(text) {
const editor = document.getElementById('editor');
editor.innerHTML += `<span>${text}</span>`;
}
// 清空编辑器
function clearEditor() {
document.getElementById('editor').innerHTML = '';
}4.7.4 搜索高亮
function highlightText(text, keyword) {
if (!keyword) return text;
const regex = new RegExp(`(${keyword})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
// 搜索并高亮
function searchAndHighlight(keyword) {
const content = document.getElementById('content');
const originalText = content.dataset.originalText || content.textContent;
content.dataset.originalText = originalText;
const highlightedText = highlightText(originalText, keyword);
content.innerHTML = highlightedText;
}
// 示例
document.getElementById('search-input').addEventListener('input', function(e) {
searchAndHighlight(e.target.value);
});4.7.5 动态卡片
function createCard(data) {
return `
<div class="card" data-id="${data.id}">
<img src="${data.image}" alt="${data.title}" class="card-image">
<div class="card-content">
<h3 class="card-title">${data.title}</h3>
<p class="card-description">${data.description}</p>
<div class="card-footer">
<span class="card-date">${data.date}</span>
<button class="btn btn-primary">阅读更多</button>
</div>
</div>
</div>
`;
}
function renderCards(cards) {
const container = document.getElementById('card-container');
container.innerHTML = cards.map(createCard).join('');
}
const articles = [
{
id: 1,
title: 'JavaScript 教程',
description: '学习 JavaScript 基础知识',
image: 'https://example.com/image1.jpg',
date: '2024-01-01'
},
{
id: 2,
title: 'CSS 实战',
description: '深入理解 CSS 样式',
image: 'https://example.com/image2.jpg',
date: '2024-01-02'
}
];
renderCards(articles);4.7.6 表单反馈
function showSuccess(message) {
const feedback = document.getElementById('feedback');
feedback.innerHTML = `
<div class="alert alert-success">
<span class="icon">✓</span>
<span>${message}</span>
</div>
`;
feedback.style.display = 'block';
}
function showError(message) {
const feedback = document.getElementById('feedback');
feedback.innerHTML = `
<div class="alert alert-error">
<span class="icon">✗</span>
<span>${message}</span>
</div>
`;
feedback.style.display = 'block';
}
function hideFeedback() {
const feedback = document.getElementById('feedback');
feedback.textContent = '';
feedback.style.display = 'none';
}
// 使用示例
if (success) {
showSuccess('提交成功!');
} else {
showError('提交失败,请重试');
}4.7.7 进度条更新
function updateProgress(percent) {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
// 更新进度条宽度
progressBar.style.width = `${percent}%`;
// 更新文本
progressText.textContent = `${percent}%`;
// 根据进度改变颜色
if (percent < 30) {
progressBar.className = 'progress-bar danger';
} else if (percent < 70) {
progressBar.className = 'progress-bar warning';
} else {
progressBar.className = 'progress-bar success';
}
}
// 模拟上传进度
function simulateUpload() {
let percent = 0;
const interval = setInterval(() => {
percent += Math.random() * 10;
if (percent >= 100) {
percent = 100;
clearInterval(interval);
}
updateProgress(Math.floor(percent));
}, 500);
}4.8 常见问题
4.8.1 textContent 和 innerText 的区别?
答案:
textContent: 返回所有文本(包括隐藏元素),不触发重排,性能更好innerText: 返回可见文本(不包括隐藏元素),触发重排,保留格式
const element = document.querySelector('#content');
console.log(element.textContent); // 所有文本
console.log(element.innerText); // 可见文本4.8.2 什么时候使用 innerHTML?
答案: 当需要插入 HTML 结构时使用 innerHTML,但要警惕 XSS 攻击。
// ✅ 安全: 使用静态 HTML
element.innerHTML = '<div class="card">内容</div>';
// ❌ 危险: 直接插入用户输入
element.innerHTML = userInput;
// ✅ 安全: 转义后插入
element.innerHTML = escapeHTML(userInput);4.8.3 如何清空元素内容?
答案: 有多种方法清空元素内容。
// 方法1: innerHTML = ''
element.innerHTML = '';
// 方法2: textContent = ''
element.textContent = '';
// 方法3: removeChild
while (element.firstChild) {
element.removeChild(element.firstChild);
}
// 推荐: 使用 innerHTML 或 textContent4.8.4 如何安全地设置内容?
答案: 根据内容类型选择合适的方法。
// 文本内容: 使用 textContent (最安全)
element.textContent = userInput;
// 需要解析 HTML: 转义后使用 innerHTML
const safeHTML = escapeHTML(userInput);
element.innerHTML = safeHTML;
// 或使用 DOM API
const text = document.createTextNode(userInput);
element.appendChild(text);4.9 总结
| 属性 | 获取内容 | 设置内容 | 安全性 | 性能 | 推荐场景 |
|---|---|---|---|---|---|
| innerText | 可见文本 | 文本 | ✅ 安全 | ⭐⭐ | 需要保留格式 |
| textContent | 所有文本 | 文本 | ✅ 安全 | ⭐⭐⭐⭐⭐ | 纯文本操作 |
| innerHTML | HTML 内容 | HTML 结构 | ⚠️ 需转义 | ⭐⭐⭐ | 需要 HTML 结构 |
| outerHTML | 完整 HTML | 替换元素 | ⚠️ 需转义 | ⭐⭐⭐ | 需要替换元素 |
最佳实践:
- ✅ 优先使用
textContent处理文本内容 - ✅ 使用
innerHTML时注意 XSS 防护 - ✅ 频繁操作时使用文档片段
- ✅ 批量操作时缓存 DOM 查询结果
- ✅ 用户输入必须转义后再插入 HTML
5. 操作元素属性
5.1 操作元素常用属性
5.1.1 常用属性列表
HTML 元素有许多常用属性,可以直接通过 JavaScript 访问和修改。
| 属性 | 说明 | 示例 |
|---|---|---|
| id | 元素的唯一标识符 | element.id |
| className | 元素的类名(字符串) | element.className |
| title | 元素的提示文本 | element.title |
| src | 图片/脚本/链接的 URL | element.src |
| href | 链接的 URL | element.href |
| alt | 图片的替代文本 | element.alt |
| value | 表单元素的值 | element.value |
| disabled | 是否禁用 | element.disabled |
| checked | 是否选中(单选/复选) | element.checked |
| selected | 是否选中(选项) | element.selected |
5.1.2 获取属性值
基本用法:
const element = document.getElementById('myElement');
// 获取常用属性
console.log(element.id); // 元素 ID
console.log(element.className); // 类名
console.log(element.title); // 提示文本
console.log(element.src); // 图片/链接 URL
console.log(element.href); // 链接 URL
console.log(element.alt); // 替代文本示例:
<img id="logo" src="logo.png" alt="Logo" title="网站Logo" class="header-logo">
<a id="link" href="https://example.com" target="_blank">链接</a>
<input type="text" id="username" value="张三">const img = document.getElementById('logo');
const link = document.getElementById('link');
const input = document.getElementById('username');
console.log(img.id); // 'logo'
console.log(img.src); // 'https://example.com/logo.png' (绝对路径)
console.log(img.alt); // 'Logo'
console.log(img.title); // '网站Logo'
console.log(img.className); // 'header-logo'
console.log(link.href); // 'https://example.com'
console.log(link.target); // '_blank'
console.log(input.value); // '张三'
console.log(input.type); // 'text'5.1.3 设置属性值
基本用法:
const element = document.getElementById('myElement');
// 设置常用属性
element.id = 'newId';
element.className = 'new-class';
element.title = '新提示';
element.src = 'new-image.png';
element.href = 'https://new-url.com';
element.alt = '新替代文本';示例:
const img = document.getElementById('logo');
const link = document.getElementById('link');
const input = document.getElementById('username');
// 修改图片
img.src = 'new-logo.png';
img.alt = '新Logo';
img.title = '新的网站Logo';
// 修改链接
link.href = 'https://newsite.com';
link.target = '_self';
// 修改输入框
input.value = '李四';
input.placeholder = '请输入用户名';
// 禁用元素
input.disabled = true;5.1.4 id 属性
获取和设置 ID:
const element = document.getElementById('myId');
// 获取 ID
console.log(element.id); // 'myId'
// 设置 ID
element.id = 'newId';注意事项:
// ✅ ID 应该是唯一的
document.getElementById('myId'); // 只返回第一个匹配的元素
// ❌ 不推荐使用特殊字符
element.id = 'my id'; // 包含空格
element.id = 'my-id'; // 使用驼峰或连字符
// ✅ 推荐的 ID 命名方式
element.id = 'userName';
element.id = 'user-name';
element.id = 'user_name';5.1.5 className 属性
获取和设置类名:
const element = document.getElementById('myElement');
// 获取类名(字符串)
console.log(element.className); // 'class1 class2 class3'
// 设置类名(会覆盖所有类)
element.className = 'new-class';
element.className = 'class1 class2 class3';
// 添加类(使用字符串拼接)
element.className += ' new-class';
// 移除类(需要字符串操作)
element.className = element.className.replace('old-class', '');注意事项:
// ❌ 直接拼接可能导致问题
element.className += ' class1'; // 如果已有 class1 会重复
// ✅ 推荐使用 classList
element.classList.add('class1', 'class2');
element.classList.remove('old-class');
element.classList.toggle('active');5.1.6 title 属性
提示文本:
const button = document.getElementById('button');
// 获取提示文本
console.log(button.title);
// 设置提示文本
button.title = '点击这里提交表单';
// 清除提示文本
button.title = '';
// 动态更新提示
function updateTitle(message) {
button.title = message;
}5.2 操作元素样式属性
5.2.1 style 属性
style 属性用于操作元素的行内样式(CSS inline style)。
基本语法:
element.style.propertyName = 'value';常用样式属性:
const element = document.getElementById('myElement');
// 颜色和背景
element.style.color = 'red';
element.style.backgroundColor = '#ffffff';
element.style.backgroundImage = 'url(image.jpg)';
// 尺寸
element.style.width = '100px';
element.style.height = '200px';
element.style.maxWidth = '500px';
// 边框
element.style.border = '1px solid black';
element.style.borderRadius = '5px';
// 内边距和外边距
element.style.padding = '10px';
element.style.margin = '20px';
element.style.marginTop = '10px';
// 定位
element.style.position = 'relative';
element.style.top = '10px';
element.style.left = '20px';
// 显示和可见性
element.style.display = 'none'; // 隐藏元素
element.style.display = 'block'; // 显示元素
element.style.visibility = 'hidden'; // 隐藏但占位
element.style.visibility = 'visible'; // 显示
// 字体
element.style.fontSize = '16px';
element.style.fontWeight = 'bold';
element.style.fontFamily = 'Arial';
// 文本
element.style.textAlign = 'center';
element.style.textDecoration = 'underline';
element.style.lineHeight = '1.5';5.2.2 CSS 属性命名规则
JavaScript 中的 CSS 属性名遵循驼峰命名法。
| CSS 属性 | JavaScript 属性 |
|---|---|
background-color | backgroundColor |
font-size | fontSize |
border-radius | borderRadius |
z-index | zIndex |
text-align | textAlign |
margin-left | marginLeft |
示例:
// CSS: background-color
element.style.backgroundColor = '#ff0000';
// CSS: font-size
element.style.fontSize = '18px';
// CSS: border-radius
element.style.borderRadius = '10px';
// CSS: z-index
element.style.zIndex = '100';
// CSS: text-align
element.style.textAlign = 'center';5.2.3 style 属性的获取
基本用法:
const element = document.getElementById('myElement');
// 获取行内样式
console.log(element.style.color); // 'red'
console.log(element.style.fontSize); // '18px'
// 注意:只能获取行内样式,无法获取 CSS 文件中的样式
console.log(element.style.width); // 如果是行内样式则返回值获取计算后的样式:
const element = document.getElementById('myElement');
// 获取计算后的样式(包括 CSS 文件中的样式)
const computedStyle = window.getComputedStyle(element);
console.log(computedStyle.color); // 'rgb(255, 0, 0)'
console.log(computedStyle.fontSize); // '18px'
console.log(computedStyle.width); // '100px'5.2.4 批量设置样式
方法1: 使用 Object.assign:
const element = document.getElementById('myElement');
Object.assign(element.style, {
color: 'red',
fontSize: '18px',
padding: '10px 20px',
borderRadius: '5px'
});方法2: 使用循环:
const element = document.getElementById('myElement');
const styles = {
color: 'red',
fontSize: '18px',
padding: '10px 20px',
borderRadius: '5px'
};
for (const [property, value] of Object.entries(styles)) {
element.style[property] = value;
}方法3: 使用 cssText:
const element = document.getElementById('myElement');
element.style.cssText = `
color: red;
font-size: 18px;
padding: 10px 20px;
border-radius: 5px;
`;
// 注意: cssText 会覆盖所有行内样式5.2.5 移除样式
方法1: 设置为空字符串:
const element = document.getElementById('myElement');
element.style.color = '';
element.style.fontSize = '';方法2: 使用 cssText 清空所有行内样式:
element.style.cssText = '';5.2.6 classList 操作样式
添加、删除、切换类名:
const element = document.getElementById('myElement');
// 添加类名
element.classList.add('active');
element.classList.add('class1', 'class2', 'class3');
// 删除类名
element.classList.remove('active');
element.classList.remove('class1', 'class2');
// 切换类名(有则删除,无则添加)
element.classList.toggle('active');
// 检查是否包含类名
if (element.classList.contains('active')) {
console.log('元素包含 active 类');
}
// 替换类名
element.classList.replace('old-class', 'new-class');实际应用:
// 切换显示/隐藏
function toggleElement(id) {
const element = document.getElementById(id);
element.classList.toggle('hidden');
}
// 添加高亮
function highlightElement(element) {
element.classList.add('highlight');
setTimeout(() => {
element.classList.remove('highlight');
}, 2000);
}
// 激活标签
function activateTab(tabElement) {
// 移除所有 active 类
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// 添加 active 类到当前标签
tabElement.classList.add('active');
}5.2.7 style 和 classList 的对比
| 方式 | 优先级 | 推荐场景 |
|---|---|---|
| style | 最高 | 动态计算样式、动画 |
| classList | 低于 style | 切换状态、主题切换 |
示例:
// ✅ 使用 classList 切换状态
element.classList.add('active');
element.classList.remove('hidden');
// ✅ 使用 style 设置动态计算的值
element.style.left = `${x}px`;
element.style.top = `${y}px`;
// ❌ 避免用 style 设置静态样式
element.style.color = 'red'; // 推荐使用 CSS 类5.3 操作表单元素属性
5.3.1 value 属性
value 属性用于获取和设置表单元素的值。
文本输入框:
const input = document.getElementById('username');
// 获取值
console.log(input.value); // '张三'
// 设置值
input.value = '李四';
// 清空值
input.value = '';
// 设置默认值
input.placeholder = '请输入用户名';文本域:
const textarea = document.getElementById('message');
// 获取值
console.log(textarea.value);
// 设置值
textarea.value = '这是消息内容';
// 追加内容
textarea.value += '\n新增内容';下拉选择框:
const select = document.getElementById('city');
// 获取选中的值
console.log(select.value); // 'beijing'
// 设置选中的值
select.value = 'shanghai';
// 获取选中的文本
const selectedOption = select.options[select.selectedIndex];
console.log(selectedOption.text);
// 获取所有选项
for (let i = 0; i < select.options.length; i++) {
console.log(select.options[i].value, select.options[i].text);
}单选按钮:
<input type="radio" name="gender" value="male" checked>男
<input type="radio" name="gender" value="female">女// 获取选中的值
const genders = document.getElementsByName('gender');
let selectedGender;
for (const radio of genders) {
if (radio.checked) {
selectedGender = radio.value;
break;
}
}
console.log(selectedGender); // 'male'
// 设置选中的值
for (const radio of genders) {
if (radio.value === 'female') {
radio.checked = true;
}
}复选框:
<input type="checkbox" name="hobby" value="reading">阅读
<input type="checkbox" name="hobby" value="music">音乐
<input type="checkbox" name="hobby" value="sports">运动// 获取所有选中的值
const hobbies = document.getElementsByName('hobby');
const selectedHobbies = [];
for (const checkbox of hobbies) {
if (checkbox.checked) {
selectedHobbies.push(checkbox.value);
}
}
console.log(selectedHobbies); // ['reading', 'music']
// 设置选中的值
const valuesToSelect = ['music', 'sports'];
for (const checkbox of hobbies) {
checkbox.checked = valuesToSelect.includes(checkbox.value);
}5.3.2 disabled 属性
禁用和启用表单元素:
const button = document.getElementById('submit-btn');
const input = document.getElementById('username');
// 禁用元素
button.disabled = true;
input.disabled = true;
// 启用元素
button.disabled = false;
input.disabled = false;
// 检查是否禁用
if (button.disabled) {
console.log('按钮已禁用');
}实际应用:
// 表单提交时禁用按钮
function submitForm() {
const button = document.getElementById('submit-btn');
button.disabled = true;
button.textContent = '提交中...';
// 模拟提交
setTimeout(() => {
button.disabled = false;
button.textContent = '提交';
}, 2000);
}
// 根据条件禁用
function toggleInput(condition) {
const input = document.getElementById('username');
input.disabled = !condition;
}5.3.3 checked 属性
单选按钮和复选框:
const checkbox = document.getElementById('agree');
const radio = document.getElementById('option1');
// 获取选中状态
console.log(checkbox.checked); // true/false
console.log(radio.checked); // true/false
// 设置选中状态
checkbox.checked = true;
checkbox.checked = false;
radio.checked = true;实际应用:
// 全选/全不选
function toggleAll(checkAll) {
const checkboxes = document.querySelectorAll('.item-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = checkAll;
});
}
// 反选
function invertSelection() {
const checkboxes = document.querySelectorAll('.item-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = !checkbox.checked;
});
}
// 检查是否有选中项
function hasSelectedItems() {
const checkboxes = document.querySelectorAll('.item-checkbox:checked');
return checkboxes.length > 0;
}5.3.4 selected 属性
下拉选项:
<select id="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
</select>const select = document.getElementById('city');
// 获取选中的选项
const selectedIndex = select.selectedIndex;
const selectedOption = select.options[selectedIndex];
console.log(selectedOption.value); // 'beijing'
console.log(selectedOption.text); // '北京'
console.log(selectedOption.selected); // true
// 设置选中的选项
select.selectedIndex = 1; // 选中第二个选项
select.value = 'guangzhou'; // 通过 value 设置
// 使用 selected 属性
select.options[2].selected = true; // 选中第三个选项5.3.5 readonly 属性
只读字段:
const input = document.getElementById('username');
// 设置为只读
input.readOnly = true;
// 取消只读
input.readOnly = false;
// 检查是否只读
if (input.readOnly) {
console.log('输入框为只读');
}实际应用:
// 显示详情时设置为只读
function viewDetails(data) {
const inputs = document.querySelectorAll('.detail-input');
inputs.forEach(input => {
input.value = data[input.name];
input.readOnly = true;
});
}
// 编辑时取消只读
function enableEdit() {
const inputs = document.querySelectorAll('.detail-input');
inputs.forEach(input => {
input.readOnly = false;
});
}5.3.6 其他表单属性
| 属性 | 说明 | 示例 |
|---|---|---|
| maxLength | 最大长度 | input.maxLength = 100 |
| minLength | 最小长度 | input.minLength = 6 |
| pattern | 正则表达式 | input.pattern = '[A-Za-z]{3,}' |
| placeholder | 占位符文本 | input.placeholder = '请输入' |
| required | 是否必填 | input.required = true |
| type | 输入类型 | input.type = 'password' |
| name | 字段名称 | input.name = 'username' |
示例:
const input = document.getElementById('username');
// 设置验证属性
input.required = true;
input.minLength = 3;
input.maxLength = 20;
input.placeholder = '请输入用户名(3-20个字符)';
input.pattern = '[a-zA-Z0-9_]+';
// 获取属性
console.log(input.name); // 'username'
console.log(input.type); // 'text'
console.log(input.minLength); // '3'
console.log(input.maxLength); // '20'5.4 自定义属性
5.4.1 data-* 属性
HTML5 引入了 data-* 自定义属性,用于存储自定义数据。
HTML 中的自定义属性:
<div id="user-card"
data-id="123"
data-name="张三"
data-age="18"
data-active="true">
用户信息
</div>
<img data-src="image.jpg" data-alt="图片描述">5.4.2 使用 dataset 访问
获取自定义属性:
const element = document.getElementById('user-card');
// 使用 dataset 获取(推荐)
console.log(element.dataset.id); // '123'
console.log(element.dataset.name); // '张三'
console.log(element.dataset.age); // '18'
console.log(element.dataset.active); // 'true'
// 使用 getAttribute 获取
console.log(element.getAttribute('data-id')); // '123'
console.log(element.getAttribute('data-name')); // '张三'设置自定义属性:
const element = document.getElementById('user-card');
// 使用 dataset 设置(推荐)
element.dataset.id = '456';
element.dataset.name = '李四';
element.dataset.newProperty = '新属性';
// 使用 setAttribute 设置
element.setAttribute('data-id', '789');
element.setAttribute('data-new-attr', 'value');删除自定义属性:
const element = document.getElementById('user-card');
// 使用 dataset 删除
delete element.dataset.active;
// 使用 removeAttribute 删除
element.removeAttribute('data-name');5.4.3 命名规则
data- 转换为 dataset*:
| HTML 属性 | dataset 属性 |
|---|---|
data-id | dataset.id |
data-user-name | dataset.userName |
dataUserId | dataset.userid (不推荐) |
示例:
<div data-user-id="123" data-user-name="张三"></div>const element = document.querySelector('div');
// ✅ 正确的访问方式
console.log(element.dataset.userId); // '123'
console.log(element.dataset.userName); // '张三'
// ❌ 错误的访问方式
console.log(element.dataset.user-id); // 语法错误5.4.4 自定义属性的应用
存储数据:
// 存储用户信息
const userCard = document.getElementById('user-card');
userCard.dataset.userId = '123';
userCard.dataset.userName = '张三';
userCard.dataset.userAge = '18';
// 获取数据
const userId = userCard.dataset.userId;
const userName = userCard.dataset.userName;事件处理:
// 存储按钮类型
<button class="btn" data-action="delete" data-id="123">删除</button>
<button class="btn" data-action="edit" data-id="123">编辑</button>const buttons = document.querySelectorAll('.btn');
buttons.forEach(button => {
button.addEventListener('click', function() {
const action = this.dataset.action;
const id = this.dataset.id;
if (action === 'delete') {
deleteItem(id);
} else if (action === 'edit') {
editItem(id);
}
});
});状态管理:
// 存储状态
const tab = document.getElementById('tab');
tab.dataset.active = 'true';
tab.dataset.index = '0';
// 检查状态
if (tab.dataset.active === 'true') {
console.log('标签处于激活状态');
}
// 转换为布尔值
const isActive = tab.dataset.active === 'true';
const index = parseInt(tab.dataset.index, 10);延迟加载:
<img data-src="image.jpg" alt="图片">// 懒加载图片
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));5.4.5 getAttribute / setAttribute / removeAttribute
通用属性操作方法:
const element = document.getElementById('myElement');
// 获取属性
const value = element.getAttribute('data-id');
const href = element.getAttribute('href');
const title = element.getAttribute('title');
// 设置属性
element.setAttribute('data-id', '123');
element.setAttribute('href', 'https://example.com');
element.setAttribute('title', '提示文本');
// 检查属性是否存在
if (element.hasAttribute('data-id')) {
console.log('data-id 属性存在');
}
// 删除属性
element.removeAttribute('data-id');
element.removeAttribute('title');dataset 与 getAttribute 的对比:
const element = document.getElementById('user-card');
// ✅ dataset:更简洁,推荐使用
element.dataset.userId = '123';
console.log(element.dataset.userId);
// ✅ getAttribute:通用方法,适用于任何属性
element.setAttribute('data-user-id', '123');
console.log(element.getAttribute('data-user-id'));
// ❌ dataset 无法访问非 data-* 属性
// element.dataset.href = '...'; // 错误
// ✅ getAttribute 可以访问任何属性
element.setAttribute('href', '...');5.4.6 实际应用示例
示例1: 列表项操作
<ul>
<li class="item" data-id="1" data-name="商品1">
<span>商品1</span>
<button class="btn-edit">编辑</button>
<button class="btn-delete">删除</button>
</li>
<li class="item" data-id="2" data-name="商品2">
<span>商品2</span>
<button class="btn-edit">编辑</button>
<button class="btn-delete">删除</button>
</li>
</ul>// 删除按钮
document.querySelectorAll('.btn-delete').forEach(button => {
button.addEventListener('click', function() {
const item = this.closest('.item');
const id = item.dataset.id;
const name = item.dataset.name;
if (confirm(`确定要删除 ${name} 吗?`)) {
deleteItem(id);
}
});
});
// 编辑按钮
document.querySelectorAll('.btn-edit').forEach(button => {
button.addEventListener('click', function() {
const item = this.closest('.item');
const id = item.dataset.id;
const name = item.dataset.name;
editItem(id, name);
});
});示例2: 表格操作
<table>
<tr data-id="1" data-name="张三">
<td>张三</td>
<td><button class="btn-view">查看</button></td>
</tr>
</table>// 点击行
document.querySelectorAll('tr[data-id]').forEach(row => {
row.addEventListener('click', function() {
const id = this.dataset.id;
const name = this.dataset.name;
showDetails(id, name);
});
});示例3: 分页
<div class="pagination">
<button data-page="1">1</button>
<button data-page="2">2</button>
<button data-page="3">3</button>
</div>document.querySelectorAll('.pagination button').forEach(button => {
button.addEventListener('click', function() {
const page = this.dataset.page;
loadPage(page);
});
});示例4: Tab 切换
<div class="tabs">
<button class="tab" data-tab="tab1" data-active="true">Tab 1</button>
<button class="tab" data-tab="tab2">Tab 2</button>
<button class="tab" data-tab="tab3">Tab 3</button>
</div>document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
const tabId = this.dataset.tab;
// 切换 tab 状态
document.querySelectorAll('.tab').forEach(t => {
t.dataset.active = 'false';
t.classList.remove('active');
});
this.dataset.active = 'true';
this.classList.add('active');
// 切换内容
switchTab(tabId);
});
});5.5 总结
5.5.1 属性操作方法对比
| 方法 | 用途 | 推荐场景 |
|---|---|---|
| element.property | 直接访问属性 | 常用属性(id, className, value, src 等) |
| element.style | 行内样式 | 动态计算样式 |
| element.classList | 类名操作 | 切换状态、主题 |
| element.dataset | data-* 属性 | 存储自定义数据 |
| getAttribute/setAttribute | 通用属性操作 | 任何属性,包括自定义属性 |
5.5.2 最佳实践
常用属性:
// ✅ 直接使用属性
element.id = 'newId';
element.className = 'new-class';
element.value = 'value';
element.disabled = true;样式操作:
// ✅ 使用 classList 切换状态
element.classList.add('active');
element.classList.toggle('hidden');
// ✅ 使用 style 设置动态值
element.style.left = `${x}px`;自定义属性:
// ✅ 使用 dataset
element.dataset.id = '123';
element.dataset.userName = '张三';通用属性:
// ✅ 使用 getAttribute/setAttribute
element.getAttribute('href');
element.setAttribute('title', '提示');5.5.3 注意事项
- ✅ 优先使用属性直接访问,而不是 getAttribute/setAttribute
- ✅ 样式优先使用 classList,而非 style
- ✅ 自定义属性使用 dataset
- ✅ 表单元素使用 checked、disabled 等布尔属性
- ❌ 避免频繁操作 style 属性,使用 classList 更好
- ❌ 注意 CSS 属性的驼峰命名规则
- ❌ data-* 属性命名避免使用大写字母
6. 定时器-间歇函数
6.1 定时器概述
JavaScript 提供了两种定时器功能:
- 间歇函数(Interval):按照指定的时间间隔重复执行代码
- 超时函数(Timeout):在指定的延迟后执行一次代码
| 类型 | 方法 | 执行次数 | 清除方法 |
|---|---|---|---|
| 间歇函数 | setInterval() | 重复执行 | clearInterval() |
| 超时函数 | setTimeout() | 执行一次 | clearTimeout() |
6.2 setInterval 间歇函数
6.2.1 基本语法
setInterval() 方法按照指定的时间间隔(毫秒)重复调用函数或代码片段。
语法:
setInterval(function, delay, arg1, arg2, ...)
// 或使用箭头函数
setInterval(() => { /* 代码 */ }, delay)
// 函数名形式
setInterval(functionName, delay)参数说明:
function: 要执行的函数或代码字符串delay: 执行间隔(毫秒),1秒 = 1000毫秒arg1, arg2, ...: 传递给函数的参数
返回值: 定时器 ID(整数),用于清除定时器
6.2.2 基本用法示例
示例1: 简单的重复执行
// 每秒打印一次
setInterval(() => {
console.log('Hello!');
}, 1000);示例2: 使用函数名
function showMessage() {
console.log('定时执行');
}
setInterval(showMessage, 1000);示例3: 传递参数
function greet(name) {
console.log(`Hello, ${name}!`);
}
setInterval(greet, 2000, '张三');示例4: 使用函数表达式
const timer = setInterval(() => {
console.log('定时器执行中...');
}, 1000);6.2.3 实际应用场景
场景1: 时钟显示
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString();
const timeElement = document.getElementById('clock');
if (timeElement) {
timeElement.textContent = timeString;
}
}
// 每秒更新一次时间
setInterval(updateTime, 1000);场景2: 倒计时
let remainingTime = 60; // 60秒倒计时
function countdown() {
const timeElement = document.getElementById('countdown');
if (timeElement) {
timeElement.textContent = `剩余时间: ${remainingTime}秒`;
}
remainingTime--;
if (remainingTime < 0) {
console.log('倒计时结束');
}
}
// 每秒更新一次倒计时
setInterval(countdown, 1000);场景3: 轮播图自动切换
let currentIndex = 0;
const slides = document.querySelectorAll('.slide');
function nextSlide() {
// 移除当前激活状态
slides[currentIndex].classList.remove('active');
// 计算下一个索引
currentIndex = (currentIndex + 1) % slides.length;
// 添加激活状态
slides[currentIndex].classList.add('active');
}
// 每3秒切换一张图片
const slideTimer = setInterval(nextSlide, 3000);场景4: 数据轮询
function fetchData() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('获取到数据:', data);
// 更新页面显示
updateDashboard(data);
})
.catch(error => {
console.error('获取数据失败:', error);
});
}
// 每5秒获取一次数据
const pollTimer = setInterval(fetchData, 5000);场景5: 动画效果
let opacity = 0;
const element = document.getElementById('fade-in');
function fadeIn() {
opacity += 0.05;
if (opacity >= 1) {
opacity = 1;
// 动画完成
}
element.style.opacity = opacity;
}
// 每50毫秒更新一次透明度
setInterval(fadeIn, 50);6.3 clearInterval 清除间歇函数
6.3.1 清除定时器
clearInterval() 方法用于停止通过 setInterval() 创建的定时器。
语法:
clearInterval(timerId)参数说明:
timerId: 由setInterval()返回的定时器 ID
6.3.2 基本用法
示例1: 清除定时器
const timer = setInterval(() => {
console.log('定时器执行中...');
}, 1000);
// 5秒后清除定时器
setTimeout(() => {
clearInterval(timer);
console.log('定时器已停止');
}, 5000);示例2: 条件清除
let count = 0;
const timer = setInterval(() => {
count++;
console.log(`执行次数: ${count}`);
// 执行5次后清除定时器
if (count >= 5) {
clearInterval(timer);
console.log('定时器已清除');
}
}, 1000);示例3: 交互式清除
let timer = null;
const button = document.getElementById('start-btn');
const stopButton = document.getElementById('stop-btn');
button.addEventListener('click', () => {
if (!timer) {
timer = setInterval(() => {
console.log('定时器运行中');
}, 1000);
button.disabled = true;
stopButton.disabled = false;
}
});
stopButton.addEventListener('click', () => {
if (timer) {
clearInterval(timer);
timer = null;
button.disabled = false;
stopButton.disabled = true;
console.log('定时器已停止');
}
});示例4: 页面卸载时清除
let timer = null;
function startTimer() {
timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
}
// 页面卸载时清除定时器
window.addEventListener('beforeunload', () => {
if (timer) {
clearInterval(timer);
}
});
startTimer();示例5: 切换暂停/继续
let timer = null;
let isRunning = false;
let count = 0;
function updateDisplay() {
const display = document.getElementById('counter');
if (display) {
display.textContent = count;
}
}
function toggleTimer() {
if (isRunning) {
// 暂停
clearInterval(timer);
timer = null;
isRunning = false;
document.getElementById('toggle-btn').textContent = '开始';
} else {
// 开始
timer = setInterval(() => {
count++;
updateDisplay();
}, 1000);
isRunning = true;
document.getElementById('toggle-btn').textContent = '暂停';
}
}
document.getElementById('toggle-btn').addEventListener('click', toggleTimer);6.4 setTimeout 超时函数
6.4.1 基本语法
setTimeout() 方法在指定的延迟(毫秒)后执行一次函数或代码片段。
语法:
setTimeout(function, delay, arg1, arg2, ...)
// 或使用箭头函数
setTimeout(() => { /* 代码 */ }, delay)
// 函数名形式
setTimeout(functionName, delay)参数说明:
function: 要执行的函数或代码字符串delay: 延迟时间(毫秒),1秒 = 1000毫秒arg1, arg2, ...: 传递给函数的参数
返回值: 定时器 ID(整数),用于清除定时器
6.4.2 基本用法示例
示例1: 延迟执行
// 1秒后执行
setTimeout(() => {
console.log('1秒后执行');
}, 1000);示例2: 使用函数名
function showMessage() {
console.log('延迟执行');
}
setTimeout(showMessage, 2000);示例3: 传递参数
function greet(name, age) {
console.log(`Hello, ${name}! 你今年${age}岁。`);
}
setTimeout(greet, 2000, '张三', 18);示例4: 立即执行
// 延迟0毫秒,实际上会尽快执行(在当前代码执行完后)
setTimeout(() => {
console.log('尽快执行');
}, 0);6.4.3 实际应用场景
场景1: 延迟提示消失
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.padding = '10px 20px';
notification.style.background = '#4CAF50';
notification.style.color = 'white';
notification.style.borderRadius = '5px';
notification.style.zIndex = '9999';
document.body.appendChild(notification);
// 3秒后自动消失
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transition = 'opacity 0.5s';
setTimeout(() => {
notification.remove();
}, 500);
}, 3000);
}
showNotification('操作成功!');场景2: 防抖(Debounce)
function debounce(func, delay) {
let timer = null;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}
// 使用示例
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
console.log('搜索:', query);
// 执行搜索逻辑
performSearch(query);
}, 500);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});场景3: 节流(Throttle)
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
// 使用示例
window.addEventListener('scroll', throttle(() => {
console.log('滚动中');
// 处理滚动事件
handleScroll();
}, 100));场景4: 异步加载
function loadContent() {
const content = document.getElementById('content');
content.textContent = '加载中...';
// 模拟异步加载
setTimeout(() => {
content.textContent = '内容加载完成!';
}, 2000);
}
loadContent();场景5: 动画延迟
function animateElement(element, delay) {
setTimeout(() => {
element.style.transition = 'all 0.5s ease';
element.style.transform = 'scale(1.2)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 500);
}, delay);
}
// 依次动画元素
const elements = document.querySelectorAll('.item');
elements.forEach((el, index) => {
animateElement(el, index * 200);
});6.5 clearTimeout 清除超时函数
6.5.1 清除定时器
clearTimeout() 方法用于停止通过 setTimeout() 创建的定时器。
语法:
clearTimeout(timerId)参数说明:
timerId: 由setTimeout()返回的定时器 ID
6.5.2 基本用法
示例1: 清除定时器
const timer = setTimeout(() => {
console.log('这行不会执行');
}, 3000);
// 1秒后清除定时器
setTimeout(() => {
clearTimeout(timer);
console.log('定时器已清除');
}, 1000);示例2: 按钮取消操作
let timeoutId = null;
const confirmButton = document.getElementById('confirm-btn');
const cancelButton = document.getElementById('cancel-btn');
confirmButton.addEventListener('click', () => {
// 显示提示
showNotification('3秒后确认操作');
// 3秒后执行操作
timeoutId = setTimeout(() => {
console.log('操作已确认');
executeOperation();
}, 3000);
cancelButton.disabled = false;
});
cancelButton.addEventListener('click', () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
console.log('操作已取消');
showNotification('操作已取消');
}
});示例3: 防抖中的清除
function debounce(func, delay) {
let timer = null;
return function(...args) {
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 设置新的定时器
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}6.6 定时器注意事项
6.6.1 时间精度问题
问题: 定时器的延迟时间不是精确的
console.log('开始:', Date.now());
setTimeout(() => {
console.log('实际延迟:', Date.now() - startTime);
}, 1000);
const startTime = Date.now();
// 实际延迟可能大于1000毫秒原因:
- 浏览器的最小时间间隔限制(通常为4ms)
- 主线程繁忙时会延迟执行
- 页面不可见时降低频率
解决方案:
// 使用 Date 对象计算实际时间
function preciseTimer(callback, interval) {
let expectedTime = Date.now() + interval;
function step() {
const drift = Date.now() - expectedTime;
if (drift >= interval) {
// 延迟过大,重新计算
expectedTime = Date.now();
}
callback();
// 修正下一次执行时间
expectedTime += interval;
setTimeout(step, Math.max(0, interval - drift));
}
setTimeout(step, interval);
}
// 使用
preciseTimer(() => {
console.log('精确执行');
}, 1000);6.6.2 this 指向问题
问题: 在普通函数中,this 可能指向不正确
const obj = {
name: '张三',
sayName: function() {
setTimeout(function() {
console.log(this.name); // undefined (严格模式) 或 window.name
}, 1000);
}
};
obj.sayName();解决方案1: 使用箭头函数
const obj = {
name: '张三',
sayName: function() {
setTimeout(() => {
console.log(this.name); // '张三'
}, 1000);
}
};解决方案2: 使用 bind
const obj = {
name: '张三',
sayName: function() {
setTimeout(function() {
console.log(this.name); // '张三'
}.bind(this), 1000);
}
};解决方案3: 保存 this
const obj = {
name: '张三',
sayName: function() {
const self = this;
setTimeout(function() {
console.log(self.name); // '张三'
}, 1000);
}
};6.6.3 内存泄漏问题
问题: 定时器未被清除可能导致内存泄漏
// ❌ 错误示例
function startTimer() {
setInterval(() => {
console.log('定时器运行');
}, 1000);
// 函数执行完,但定时器仍在运行
}
startTimer();
// 如果多次调用 startTimer(),会创建多个定时器解决方案:
// ✅ 正确示例
let timer = null;
function startTimer() {
// 清除之前的定时器
if (timer) {
clearInterval(timer);
}
// 创建新定时器
timer = setInterval(() => {
console.log('定时器运行');
}, 1000);
}
// 页面卸载时清除
window.addEventListener('beforeunload', () => {
if (timer) {
clearInterval(timer);
}
});6.6.4 页面可见性
问题: 页面不可见时定时器仍在运行
解决方案:
let timer = null;
let isVisible = true;
function startTimer() {
if (!isVisible) return;
timer = setInterval(() => {
console.log('定时器运行');
}, 1000);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
// 监听页面可见性
document.addEventListener('visibilitychange', () => {
isVisible = !document.hidden;
if (isVisible) {
startTimer();
} else {
stopTimer();
}
});
startTimer();6.7 定时器最佳实践
6.7.1 保存定时器 ID
// ✅ 好的做法: 保存定时器 ID
let timerId = null;
function startTimer() {
timerId = setInterval(() => {
console.log('执行任务');
}, 1000);
}
function stopTimer() {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
}
// ❌ 不好的做法: 没有保存定时器 ID
function startTimer() {
setInterval(() => {
console.log('执行任务');
}, 1000);
// 无法清除定时器
}6.7.2 清除之前的定时器
// ✅ 好的做法: 清除之前的定时器
let timer = null;
function restartTimer() {
if (timer) {
clearInterval(timer);
}
timer = setInterval(() => {
console.log('新的定时器');
}, 1000);
}6.7.3 页面卸载时清除定时器
let timer = null;
function startTimer() {
timer = setInterval(() => {
console.log('定时器运行');
}, 1000);
}
// 页面卸载时清除
window.addEventListener('beforeunload', () => {
if (timer) {
clearInterval(timer);
}
});
startTimer();6.7.4 使用 requestAnimationFrame 替代定时器做动画
// ❌ 不好的做法: 使用 setInterval 做动画
function animate() {
element.style.left = parseInt(element.style.left) + 1 + 'px';
}
setInterval(animate, 16); // 约60fps
// ✅ 好的做法: 使用 requestAnimationFrame
function animate() {
element.style.left = parseInt(element.style.left) + 1 + 'px';
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);6.8 实际应用示例
6.8.1 秒表计时器
let seconds = 0;
let minutes = 0;
let timer = null;
let isRunning = false;
function updateDisplay() {
const display = document.getElementById('stopwatch');
const formatted = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
display.textContent = formatted;
}
function tick() {
seconds++;
if (seconds >= 60) {
seconds = 0;
minutes++;
}
updateDisplay();
}
function start() {
if (!isRunning) {
timer = setInterval(tick, 1000);
isRunning = true;
document.getElementById('start-btn').disabled = true;
document.getElementById('pause-btn').disabled = false;
}
}
function pause() {
if (isRunning) {
clearInterval(timer);
isRunning = false;
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
}
}
function reset() {
pause();
seconds = 0;
minutes = 0;
updateDisplay();
}
document.getElementById('start-btn').addEventListener('click', start);
document.getElementById('pause-btn').addEventListener('click', pause);
document.getElementById('reset-btn').addEventListener('click', reset);6.8.2 自动保存
let saveTimer = null;
function autoSave() {
const content = document.getElementById('editor').value;
// 保存到本地存储
localStorage.setItem('draft', content);
// 显示保存提示
showNotification('已自动保存');
}
function debounceSave(delay = 3000) {
// 清除之前的定时器
if (saveTimer) {
clearTimeout(saveTimer);
}
// 设置新的定时器
saveTimer = setTimeout(() => {
autoSave();
}, delay);
}
// 监听输入事件
document.getElementById('editor').addEventListener('input', () => {
debounceSave();
});
// 页面卸载时清除
window.addEventListener('beforeunload', () => {
if (saveTimer) {
clearTimeout(saveTimer);
autoSave();
}
});6.8.3 轮询等待
function pollUntilComplete(condition, callback, interval = 1000, maxAttempts = 10) {
let attempts = 0;
function check() {
attempts++;
if (condition()) {
// 条件满足
callback(null, true);
} else if (attempts >= maxAttempts) {
// 超过最大尝试次数
callback(new Error('超时'), false);
} else {
// 继续等待
setTimeout(check, interval);
}
}
check();
}
// 使用示例
let isReady = false;
// 模拟异步操作
setTimeout(() => {
isReady = true;
}, 5000);
// 轮询等待
pollUntilComplete(
() => isReady,
(error, success) => {
if (success) {
console.log('条件已满足');
} else {
console.error('等待超时');
}
},
1000,
10
);6.8.4 进度条动画
function animateProgress(element, duration, target) {
const startTime = Date.now();
const startValue = parseInt(element.style.width) || 0;
function update() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// 计算当前值
const currentValue = startValue + (target - startValue) * progress;
element.style.width = `${currentValue}%`;
element.textContent = `${Math.round(currentValue)}%`;
// 继续动画
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
// 使用示例
const progressBar = document.getElementById('progress');
animateProgress(progressBar, 2000, 100);6.8.5 自动滚动
let scrollTimer = null;
let isScrolling = false;
function autoScroll() {
const container = document.getElementById('scroll-container');
const scrollStep = 2;
container.scrollTop += scrollStep;
// 到达底部时回到顶部
if (container.scrollTop + container.clientHeight >= container.scrollHeight) {
container.scrollTop = 0;
}
}
function startAutoScroll() {
if (!isScrolling) {
scrollTimer = setInterval(autoScroll, 50);
isScrolling = true;
}
}
function stopAutoScroll() {
if (isScrolling) {
clearInterval(scrollTimer);
scrollTimer = null;
isScrolling = false;
}
}
// 鼠标悬停时暂停,离开时继续
const container = document.getElementById('scroll-container');
container.addEventListener('mouseenter', stopAutoScroll);
container.addEventListener('mouseleave', startAutoScroll);
// 开始自动滚动
startAutoScroll();6.9 总结对比表
| 特性 | setInterval | setTimeout |
|---|---|---|
| 执行次数 | 重复执行 | 执行一次 |
| 清除方法 | clearInterval | clearTimeout |
| 返回值 | 定时器 ID | 定时器 ID |
| 适用场景 | 定时任务、轮询、动画 | 延迟执行、防抖、异步操作 |
6.10 常见问题
6.10.1 setInterval 和 setTimeout 的区别是什么?
答案:
setInterval: 重复执行,需要用clearInterval清除setTimeout: 执行一次,需要用clearTimeout清除
// setInterval: 重复执行
setInterval(() => console.log('重复'), 1000);
// setTimeout: 执行一次
setTimeout(() => console.log('一次'), 1000);6.10.2 如何停止定时器?
答案: 保存定时器 ID,然后使用对应的清除方法。
// 停止 setInterval
const timer1 = setInterval(callback, 1000);
clearInterval(timer1);
// 停止 setTimeout
const timer2 = setTimeout(callback, 1000);
clearTimeout(timer2);6.10.3 定时器的时间精确吗?
答案: 不精确。实际延迟可能大于指定时间,受以下因素影响:
- 浏览器的最小时间间隔(4ms)
- 主线程繁忙程度
- 页面可见性
如需精确计时,使用 Date 对象计算实际时间。
6.10.4 页面卸载时定时器会自动清除吗?
答案: 不会。建议在 beforeunload 事件中手动清除。
window.addEventListener('beforeunload', () => {
if (timer) {
clearInterval(timer);
}
});6.10.5 如何实现防抖和节流?
答案:
// 防抖
function debounce(func, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}