Skip to content

1. let 和 const 的声明时的区别

1.1 变量声明的发展历程

在 JavaScript 发展过程中,变量的声明方式经历了三个阶段:

javascript
// ES5 之前: 使用 var
var name = '张三';
var age = 18;

// ES6 (2015): 新增 let 和 const
let name = '张三';
const age = 18;

// 现代开发: 推荐使用 let 和 const,避免使用 var

1.2 let 和 const 的基本区别

特性letconst
可变性可以重新赋值声明后不可重新赋值
初始化可以不赋初始值必须赋初始值
块级作用域
暂时性死区
重复声明❌ 不允许❌ 不允许
使用场景需要变化的变量不变的常量

1.3 const 的特点

1.3.1 必须初始化

const 声明的变量必须立即初始化,否则会报错:

javascript
// ❌ 错误: 缺少初始值
const name;
// SyntaxError: Missing initializer in const declaration

// ✅ 正确: 声明时必须赋值
const name = '张三';
const age = 18;
const PI = 3.141592653;

1.3.2 不可重新赋值

const 声明的变量不能重新赋值:

javascript
const name = '张三';

// ❌ 错误: 不能重新赋值
name = '李四';
// TypeError: Assignment to constant variable.

const age = 18;
age = 19;
// TypeError: Assignment to constant variable.

1.3.3 const 声明对象的注意事项

重要: const 声明的对象本身不能被重新赋值,但对象内部的属性可以被修改:

javascript
// 声明对象
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.

数组同理:

javascript
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 声明的变量可以不立即赋值:

javascript
// ✅ 正确: 可以不赋初始值
let name;

name = '张三';
console.log(name); // 张三

let age;
console.log(age); // undefined

age = 18;
console.log(age); // 18

1.4.2 可以重新赋值

let 声明的变量可以被重新赋值:

javascript
let name = '张三';

// ✅ 正确: 可以重新赋值
name = '李四';
console.log(name); // 李四

name = '王五';
console.log(name); // 王五

let count = 0;
count++;
count += 1;
console.log(count); // 2

1.5 块级作用域

letconst 都具有块级作用域,而 var 只有函数作用域:

1.5.1 let 和 const 的块级作用域

javascript
// 块级作用域示例
{
    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 defined

1.5.2 var 和 let/const 的区别

javascript
// 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 循环中的变量声明

javascript
// 使用 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)

letconst 声明的变量存在暂时性死区(Temporal Dead Zone),在声明之前无法访问:

javascript
// ❌ 错误: 在声明前访问
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 暂时性死区的示例

javascript
function test() {
    // 暂时性死区开始
    console.log(x); // ReferenceError: Cannot access 'x' before initialization

    let x = 10;
    // 暂时性死区结束

    console.log(x); // ✅ 10
}

test();

1.7 重复声明

letconst 不允许重复声明:

javascript
// ❌ 错误: 使用 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 的对比:

javascript
// var 可以重复声明(但不推荐)
var name = '张三';
var name = '李四';
console.log(name); // 李四

// 但 let 和 const 不允许
let name = '张三';
let name = '李四';
// SyntaxError: Identifier 'name' has already been declared

1.8 实际应用场景

1.8.1 何时使用 const

javascript
// 场景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

javascript
// 场景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 变量声明原则

javascript
// ✅ 默认使用 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 命名规范

javascript
// 常量: 使用全大写
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 代码示例对比

javascript
// ❌ 不好的写法
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 总结对比表

特性letconst
是否必须初始化❌ 不必须✅ 必须
是否可以重新赋值✅ 可以❌ 不可以
是否具有块级作用域✅ 是✅ 是
是否存在暂时性死区✅ 是✅ 是
是否可以重复声明❌ 不可以❌ 不可以
声明对象的属性可修改✅ 可以✅ 可以
声明数组的元素可修改✅ 可以✅ 可以
推荐使用场景需要变化的变量常量和不变的数据

1.11 常见问题

1.11.1 const 声明的对象可以修改吗?

答案: 可以修改对象的属性,但不能重新赋值整个对象。

javascript
const person = { name: '张三', age: 18 };
person.name = '李四'; // ✅ 可以
person.age = 19;      // ✅ 可以
person = {};          // ❌ 不可以

1.11.2 应该优先使用 let 还是 const?

答案: 优先使用 const,只有在需要重新赋值时才使用 let。

javascript
// ✅ 默认使用 const
const name = '张三';
const age = 18;

// 需要重新赋值时使用 let
let count = 0;
count++;

1.11.3 什么时候使用 var?

答案: 现代开发中几乎不使用 var,统一使用 let 和 const。只有在维护旧代码时可能会遇到 var。

1.11.4 const 声明的数组可以修改吗?

答案: 可以修改数组的内容,但不能重新赋值整个数组。

javascript
const arr = [1, 2, 3];
arr.push(4); // ✅ 可以
arr[0] = 10; // ✅ 可以
arr = [];     // ❌ 不可以

1.11.5 为什么 const 更推荐使用?

答案:

  1. 代码更安全: 防止意外重新赋值
  2. 更易维护: 一旦声明就知道值不会改变
  3. 更好的性能: 引擎可以进行优化
  4. 更清晰的意图: 明确表示这是常量


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 APIdocumentquerySelectorcreateElement操作网页文档结构
BOM APIwindowlocationnavigatorhistory操作浏览器窗口和导航
事件 APIaddEventListenerremoveEventListener处理用户交互和浏览器事件
网络 APIfetchXMLHttpRequestWebSocket发送网络请求
存储 APIlocalStoragesessionStorageIndexedDB在浏览器中存储数据
定时器 APIsetTimeoutsetIntervalrequestAnimationFrame延迟和周期性执行任务
Canvas APIgetContext('2d')getImageData2D/3D 图形绘制
地理 APInavigator.geolocation获取用户地理位置
多媒体 APIMediaRecorderAudioContext处理音频、视频
通知 APINotificationService Worker显示通知和消息

分类方式二: W3C 标准分类

javascript
// 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 的特点

javascript
// 特点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 访问和操作。

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 的主要作用:

  1. 访问页面元素: 查找、选择 HTML 元素
  2. 修改元素内容: 改变文本、HTML 内容
  3. 修改元素样式: 修改 CSS 样式
  4. 创建和删除元素: 动态添加或移除元素
  5. 响应事件: 处理用户的点击、输入等操作

示例:

javascript
// 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 的类型

javascript
// 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); // true

2.3 DOM 树

DOM 将 HTML 文档表示为树形结构,称为 DOM 树。

2.3.1 DOM 树的结构

img_18.pngimg_19.png

html
<!-- 示例 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>&copy; 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 节点关系

javascript
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 树的遍历

javascript
// 递归遍历 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 树的操作

javascript
// 添加子节点
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 提供了多个对象来操作文档的不同部分。

html
<!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>

img_20.png

2.4.1 document 对象

document 对象代表整个 HTML 文档,是 DOM 树的根节点。

常用属性:

javascript
// 文档信息
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

常用方法:

javascript
// 查找元素
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();                         // 关闭文档流

示例:

javascript
// 查找元素
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 元素的基类。

常用属性:

javascript
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');        // 替换类

示例:

javascript
// 操作类名
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 是节点的集合,类似数组但不是真正的数组。

javascript
// 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 元素的集合,也是类数组对象。

javascript
// 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 操作最佳实践

javascript
// 最佳实践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: 动态创建列表

javascript
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: 表单验证

javascript
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: 图片懒加载

javascript
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 选择器的第一个元素。

基本语法:

javascript
document.querySelector(selector)
element.querySelector(selector)

常用示例:

javascript
// 通过 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');

在特定元素内查找:

javascript
const container = document.getElementById('container');

// 只在 container 内部查找
const firstParagraph = container.querySelector('p');
const activeButton = container.querySelector('.btn.active');

注意事项:

javascript
// 如果找不到元素,返回 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。

基本语法:

javascript
document.querySelectorAll(selector)
element.querySelectorAll(selector)

常用示例:

javascript
// 获取所有 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)');

在特定元素内查找:

javascript
const container = document.getElementById('container');

// 只在 container 内部查找
const paragraphs = container.querySelectorAll('p');
console.log(paragraphs.length); // container 内的 p 元素数量

遍历 NodeList:

javascript
// 方式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 的特点:

javascript
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 返回的是静态 NodeList

3.2 其他获取 DOM 元素方法

3.2.1 getElementById 方法

通过元素的 ID 属性获取元素,返回单个元素。

语法:

javascript
document.getElementById(id)

示例:

javascript
// 基本用法
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';

注意事项:

javascript
// 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(动态集合)。

语法:

javascript
document.getElementsByClassName(className)
element.getElementsByClassName(className)

示例:

javascript
// 获取所有具有指定类名的元素
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 的特点:

javascript
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(动态集合)。

语法:

javascript
document.getElementsByTagName(tagName)
element.getElementsByTagName(tagName)

示例:

javascript
// 获取所有 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);
}

特殊用法:

javascript
// 使用 '*' 获取所有元素
const allElements = document.getElementsByTagName('*');
console.log(allElements.length); // 文档中所有元素的数量

// 获取特定类型的所有元素
const allInputs = document.getElementsByTagName('input');
const allButtons = document.getElementsByTagName('button');

3.2.4 getElementsByName 方法

通过 name 属性获取元素集合,返回 NodeList(动态集合)。

语法:

javascript
document.getElementsByName(name)

示例:

javascript
// 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'

典型应用场景:

javascript
// 单选按钮组
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 集合:

javascript
// 获取所有表单
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 集合:

javascript
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);
}

其他表单集合:

javascript
// 获取所有图片
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 选择器⭐⭐⭐ 较快
getElementsByClassNameHTMLCollection仅类名⭐⭐⭐⭐ 快
getElementsByTagNameHTMLCollection仅标签名⭐⭐⭐⭐ 快
getElementsByNameNodeListname 属性⭐⭐⭐ 较快
querySelectorAllNodeListCSS 选择器⭐⭐ 较慢

使用建议:

javascript
// ✅ 推荐用法
// 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: 获取表单数据

javascript
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: 批量操作元素

javascript
// 给所有按钮添加点击事件
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: 表格操作

javascript
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: 图片懒加载检测

javascript
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: 表单验证

javascript
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: 导航菜单高亮

javascript
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 选择器复杂选择器获取单个元素
querySelectorAllNodeListCSS 选择器复杂选择器获取多个元素
getElementsByClassNameHTMLCollection类名按类名获取元素
getElementsByTagNameHTMLCollection标签名按标签名获取元素
getElementsByNameNodeListname 属性按表单 name 获取元素

3.4 最佳实践

3.4.1 性能优化

javascript
// ✅ 缓存 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 选择器优化

javascript
// ✅ 使用更具体的选择器
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 错误处理

javascript
// ✅ 总是检查元素是否存在
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 获取文本内容

基本用法:

javascript
const element = document.getElementById('content');

// 获取文本内容
const text = element.innerText;
console.log(text);

特点:

html
<div id="content">
    <h1>标题</h1>
    <p>这是一段<strong>加粗</strong>文本</p>
    <p style="display: none;">这是隐藏的文本</p>
</div>
javascript
const element = document.getElementById('content');
const text = element.innerText;
console.log(text);
/*
输出:
标题
这是一段加粗文本
*/

innerText 的特点:

  • ✅ 保留文本格式(换行、空格等)
  • ✅ 会触发重排(reflow),性能较低
  • ✅ 不返回隐藏元素的文本(display: none)
  • ✅ 类似用户看到的内容
  • ❌ 不是标准属性(被 innerText 替代)

4.1.2 设置文本内容

基本用法:

javascript
const element = document.getElementById('title');

// 设置文本内容
element.innerText = '新标题';

// 清空内容
element.innerText = '';

示例:

javascript
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 与样式的关系

javascript
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 获取文本内容

基本用法:

javascript
const element = document.getElementById('content');

// 获取所有文本内容
const text = element.textContent;
console.log(text);

特点:

html
<div id="content">
    <h1>标题</h1>
    <p>这是一段<strong>加粗</strong>文本</p>
    <p style="display: none;">这是隐藏的文本</p>
</div>
javascript
const element = document.getElementById('content');
const text = element.textContent;
console.log(text);
/*
输出:
标题
这是一段加粗文本
这是隐藏的文本
*/

textContent 的特点:

  • ✅ 返回所有文本,包括隐藏元素
  • ✅ 不保留格式,所有文本合并
  • ✅ 不会触发重排,性能较高
  • ✅ 标准属性,推荐使用
  • ❌ 不考虑 CSS 样式影响

4.2.2 设置文本内容

基本用法:

javascript
const element = document.getElementById('title');

// 设置文本内容
element.textContent = '新标题';

// 清空内容
element.textContent = '';

示例:

javascript
const card = document.createElement('div');
card.innerHTML = `
    <h3>卡片标题</h3>
    <p>这是一段描述</p>
`;

// 使用 textContent 替换所有内容
card.textContent = '新内容';
console.log(card.innerHTML); // '新内容' (所有子元素被移除)

4.2.3 textContent 性能优势

javascript
// ✅ 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 内容

基本用法:

javascript
const element = document.getElementById('content');

// 获取 HTML 内容
const html = element.innerHTML;
console.log(html);

示例:

html
<div id="content">
    <h1>标题</h1>
    <p>段落文本</p>
</div>
javascript
const element = document.getElementById('content');
const html = element.innerHTML;
console.log(html);
/*
输出:
    <h1>标题</h1>
    <p>段落文本</p>
*/

4.3.2 设置 HTML 内容

基本用法:

javascript
const element = document.getElementById('content');

// 设置 HTML 内容
element.innerHTML = '<h2>新标题</h2><p>新段落</p>';

// 清空内容
element.innerHTML = '';

动态创建 HTML:

javascript
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 模板字符串使用

javascript
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 攻击风险:

javascript
// ❌ 危险: 直接插入用户输入
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 事件!

✅ 安全的做法:

javascript
// 方法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

html
<div id="container">
    <h1>标题</h1>
    <p>内容</p>
</div>
javascript
const element = document.getElementById('container');

// 获取包括元素本身的 HTML
const html = element.outerHTML;
console.log(html);
/*
输出:
<div id="container">
    <h1>标题</h1>
    <p>内容</p>
</div>
*/

4.4.2 替换元素

javascript
const oldElement = document.getElementById('old-element');

// 替换元素及其内容
oldElement.outerHTML = '<div id="new-element">新元素</div>';

// 旧元素已从 DOM 中移除
console.log(oldElement.parentElement); // null

示例:

javascript
// 动态更新元素
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⭐⭐⭐ 中等

对比示例:

html
<div id="content" style="display: none;">
    <h1>标题</h1>
    <p>段落 <strong>加粗</strong> 文本</p>
</div>
javascript
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 选择合适的属性

javascript
// ✅ 场景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 性能优化

javascript
// ✅ 批量操作时使用文档片段
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

javascript
// ✅ 安全的模板函数
function safeHTML(strings, ...values) {
    const escaped = values.map(value => {
        if (typeof value !== 'string') return value;
        return value.replace(/&/g, '&amp;')
                   .replace(/</g, '&lt;')
                   .replace(/>/g, '&gt;')
                   .replace(/"/g, '&quot;')
                   .replace(/'/g, '&#39;');
    });
    
    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>&lt;script&gt;alert(1)&lt;/script&gt;</div>

4.7 实际应用示例

4.7.1 动态生成列表

javascript
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 数据渲染

javascript
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 富文本编辑器

javascript
// 获取编辑器内容
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 搜索高亮

javascript
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 动态卡片

javascript
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 表单反馈

javascript
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 进度条更新

javascript
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: 返回可见文本(不包括隐藏元素),触发重排,保留格式
javascript
const element = document.querySelector('#content');

console.log(element.textContent); // 所有文本
console.log(element.innerText);  // 可见文本

4.8.2 什么时候使用 innerHTML?

答案: 当需要插入 HTML 结构时使用 innerHTML,但要警惕 XSS 攻击。

javascript
// ✅ 安全: 使用静态 HTML
element.innerHTML = '<div class="card">内容</div>';

// ❌ 危险: 直接插入用户输入
element.innerHTML = userInput;

// ✅ 安全: 转义后插入
element.innerHTML = escapeHTML(userInput);

4.8.3 如何清空元素内容?

答案: 有多种方法清空元素内容。

javascript
// 方法1: innerHTML = ''
element.innerHTML = '';

// 方法2: textContent = ''
element.textContent = '';

// 方法3: removeChild
while (element.firstChild) {
    element.removeChild(element.firstChild);
}

// 推荐: 使用 innerHTML 或 textContent

4.8.4 如何安全地设置内容?

答案: 根据内容类型选择合适的方法。

javascript
// 文本内容: 使用 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所有文本文本✅ 安全⭐⭐⭐⭐⭐纯文本操作
innerHTMLHTML 内容HTML 结构⚠️ 需转义⭐⭐⭐需要 HTML 结构
outerHTML完整 HTML替换元素⚠️ 需转义⭐⭐⭐需要替换元素

最佳实践:

  1. ✅ 优先使用 textContent 处理文本内容
  2. ✅ 使用 innerHTML 时注意 XSS 防护
  3. ✅ 频繁操作时使用文档片段
  4. ✅ 批量操作时缓存 DOM 查询结果
  5. ✅ 用户输入必须转义后再插入 HTML

5. 操作元素属性

5.1 操作元素常用属性

5.1.1 常用属性列表

HTML 元素有许多常用属性,可以直接通过 JavaScript 访问和修改。

属性说明示例
id元素的唯一标识符element.id
className元素的类名(字符串)element.className
title元素的提示文本element.title
src图片/脚本/链接的 URLelement.src
href链接的 URLelement.href
alt图片的替代文本element.alt
value表单元素的值element.value
disabled是否禁用element.disabled
checked是否选中(单选/复选)element.checked
selected是否选中(选项)element.selected

5.1.2 获取属性值

基本用法:

javascript
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);         // 替代文本

示例:

html
<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="张三">
javascript
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 设置属性值

基本用法:

javascript
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 = '新替代文本';

示例:

javascript
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:

javascript
const element = document.getElementById('myId');

// 获取 ID
console.log(element.id); // 'myId'

// 设置 ID
element.id = 'newId';

注意事项:

javascript
// ✅ 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 属性

获取和设置类名:

javascript
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', '');

注意事项:

javascript
// ❌ 直接拼接可能导致问题
element.className += ' class1'; // 如果已有 class1 会重复

// ✅ 推荐使用 classList
element.classList.add('class1', 'class2');
element.classList.remove('old-class');
element.classList.toggle('active');

5.1.6 title 属性

提示文本:

javascript
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)。

基本语法:

javascript
element.style.propertyName = 'value';

常用样式属性:

javascript
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-colorbackgroundColor
font-sizefontSize
border-radiusborderRadius
z-indexzIndex
text-aligntextAlign
margin-leftmarginLeft

示例:

javascript
// 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 属性的获取

基本用法:

javascript
const element = document.getElementById('myElement');

// 获取行内样式
console.log(element.style.color);       // 'red'
console.log(element.style.fontSize);    // '18px'

// 注意:只能获取行内样式,无法获取 CSS 文件中的样式
console.log(element.style.width);       // 如果是行内样式则返回值

获取计算后的样式:

javascript
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:

javascript
const element = document.getElementById('myElement');

Object.assign(element.style, {
    color: 'red',
    fontSize: '18px',
    padding: '10px 20px',
    borderRadius: '5px'
});

方法2: 使用循环:

javascript
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:

javascript
const element = document.getElementById('myElement');

element.style.cssText = `
    color: red;
    font-size: 18px;
    padding: 10px 20px;
    border-radius: 5px;
`;

// 注意: cssText 会覆盖所有行内样式

5.2.5 移除样式

方法1: 设置为空字符串:

javascript
const element = document.getElementById('myElement');

element.style.color = '';
element.style.fontSize = '';

方法2: 使用 cssText 清空所有行内样式:

javascript
element.style.cssText = '';

5.2.6 classList 操作样式

添加、删除、切换类名:

javascript
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');

实际应用:

javascript
// 切换显示/隐藏
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切换状态、主题切换

示例:

javascript
// ✅ 使用 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 属性用于获取和设置表单元素的值。

文本输入框:

javascript
const input = document.getElementById('username');

// 获取值
console.log(input.value); // '张三'

// 设置值
input.value = '李四';

// 清空值
input.value = '';

// 设置默认值
input.placeholder = '请输入用户名';

文本域:

javascript
const textarea = document.getElementById('message');

// 获取值
console.log(textarea.value);

// 设置值
textarea.value = '这是消息内容';

// 追加内容
textarea.value += '\n新增内容';

下拉选择框:

javascript
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);
}

单选按钮:

html
<input type="radio" name="gender" value="male" checked>男
<input type="radio" name="gender" value="female">女
javascript
// 获取选中的值
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;
    }
}

复选框:

html
<input type="checkbox" name="hobby" value="reading">阅读
<input type="checkbox" name="hobby" value="music">音乐
<input type="checkbox" name="hobby" value="sports">运动
javascript
// 获取所有选中的值
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 属性

禁用和启用表单元素:

javascript
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('按钮已禁用');
}

实际应用:

javascript
// 表单提交时禁用按钮
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 属性

单选按钮和复选框:

javascript
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;

实际应用:

javascript
// 全选/全不选
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 属性

下拉选项:

html
<select id="city">
    <option value="beijing">北京</option>
    <option value="shanghai">上海</option>
    <option value="guangzhou">广州</option>
</select>
javascript
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 属性

只读字段:

javascript
const input = document.getElementById('username');

// 设置为只读
input.readOnly = true;

// 取消只读
input.readOnly = false;

// 检查是否只读
if (input.readOnly) {
    console.log('输入框为只读');
}

实际应用:

javascript
// 显示详情时设置为只读
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'

示例:

javascript
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 中的自定义属性:

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 访问

获取自定义属性:

javascript
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'));    // '张三'

设置自定义属性:

javascript
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');

删除自定义属性:

javascript
const element = document.getElementById('user-card');

// 使用 dataset 删除
delete element.dataset.active;

// 使用 removeAttribute 删除
element.removeAttribute('data-name');

5.4.3 命名规则

data- 转换为 dataset*:

HTML 属性dataset 属性
data-iddataset.id
data-user-namedataset.userName
dataUserIddataset.userid (不推荐)

示例:

html
<div data-user-id="123" data-user-name="张三"></div>
javascript
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 自定义属性的应用

存储数据:

javascript
// 存储用户信息
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;

事件处理:

javascript
// 存储按钮类型
<button class="btn" data-action="delete" data-id="123">删除</button>
<button class="btn" data-action="edit" data-id="123">编辑</button>
javascript
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);
        }
    });
});

状态管理:

javascript
// 存储状态
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);

延迟加载:

html
<img data-src="image.jpg" alt="图片">
javascript
// 懒加载图片
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

通用属性操作方法:

javascript
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 的对比:

javascript
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: 列表项操作

html
<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>
javascript
// 删除按钮
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: 表格操作

html
<table>
    <tr data-id="1" data-name="张三">
        <td>张三</td>
        <td><button class="btn-view">查看</button></td>
    </tr>
</table>
javascript
// 点击行
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: 分页

html
<div class="pagination">
    <button data-page="1">1</button>
    <button data-page="2">2</button>
    <button data-page="3">3</button>
</div>
javascript
document.querySelectorAll('.pagination button').forEach(button => {
    button.addEventListener('click', function() {
        const page = this.dataset.page;
        loadPage(page);
    });
});

示例4: Tab 切换

html
<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>
javascript
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.datasetdata-* 属性存储自定义数据
getAttribute/setAttribute通用属性操作任何属性,包括自定义属性

5.5.2 最佳实践

常用属性:

javascript
// ✅ 直接使用属性
element.id = 'newId';
element.className = 'new-class';
element.value = 'value';
element.disabled = true;

样式操作:

javascript
// ✅ 使用 classList 切换状态
element.classList.add('active');
element.classList.toggle('hidden');

// ✅ 使用 style 设置动态值
element.style.left = `${x}px`;

自定义属性:

javascript
// ✅ 使用 dataset
element.dataset.id = '123';
element.dataset.userName = '张三';

通用属性:

javascript
// ✅ 使用 getAttribute/setAttribute
element.getAttribute('href');
element.setAttribute('title', '提示');

5.5.3 注意事项

  1. ✅ 优先使用属性直接访问,而不是 getAttribute/setAttribute
  2. ✅ 样式优先使用 classList,而非 style
  3. ✅ 自定义属性使用 dataset
  4. ✅ 表单元素使用 checked、disabled 等布尔属性
  5. ❌ 避免频繁操作 style 属性,使用 classList 更好
  6. ❌ 注意 CSS 属性的驼峰命名规则
  7. ❌ data-* 属性命名避免使用大写字母

6. 定时器-间歇函数

6.1 定时器概述

JavaScript 提供了两种定时器功能:

  • 间歇函数(Interval):按照指定的时间间隔重复执行代码
  • 超时函数(Timeout):在指定的延迟后执行一次代码
类型方法执行次数清除方法
间歇函数setInterval()重复执行clearInterval()
超时函数setTimeout()执行一次clearTimeout()

6.2 setInterval 间歇函数

6.2.1 基本语法

setInterval() 方法按照指定的时间间隔(毫秒)重复调用函数或代码片段。

语法:

javascript
setInterval(function, delay, arg1, arg2, ...)

// 或使用箭头函数
setInterval(() => { /* 代码 */ }, delay)

// 函数名形式
setInterval(functionName, delay)

参数说明:

  • function: 要执行的函数或代码字符串
  • delay: 执行间隔(毫秒),1秒 = 1000毫秒
  • arg1, arg2, ...: 传递给函数的参数

返回值: 定时器 ID(整数),用于清除定时器

6.2.2 基本用法示例

示例1: 简单的重复执行

javascript
// 每秒打印一次
setInterval(() => {
    console.log('Hello!');
}, 1000);

示例2: 使用函数名

javascript
function showMessage() {
    console.log('定时执行');
}

setInterval(showMessage, 1000);

示例3: 传递参数

javascript
function greet(name) {
    console.log(`Hello, ${name}!`);
}

setInterval(greet, 2000, '张三');

示例4: 使用函数表达式

javascript
const timer = setInterval(() => {
    console.log('定时器执行中...');
}, 1000);

6.2.3 实际应用场景

场景1: 时钟显示

javascript
function updateTime() {
    const now = new Date();
    const timeString = now.toLocaleTimeString();
    
    const timeElement = document.getElementById('clock');
    if (timeElement) {
        timeElement.textContent = timeString;
    }
}

// 每秒更新一次时间
setInterval(updateTime, 1000);

场景2: 倒计时

javascript
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: 轮播图自动切换

javascript
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: 数据轮询

javascript
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: 动画效果

javascript
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() 创建的定时器。

语法:

javascript
clearInterval(timerId)

参数说明:

  • timerId: 由 setInterval() 返回的定时器 ID

6.3.2 基本用法

示例1: 清除定时器

javascript
const timer = setInterval(() => {
    console.log('定时器执行中...');
}, 1000);

// 5秒后清除定时器
setTimeout(() => {
    clearInterval(timer);
    console.log('定时器已停止');
}, 5000);

示例2: 条件清除

javascript
let count = 0;

const timer = setInterval(() => {
    count++;
    console.log(`执行次数: ${count}`);
    
    // 执行5次后清除定时器
    if (count >= 5) {
        clearInterval(timer);
        console.log('定时器已清除');
    }
}, 1000);

示例3: 交互式清除

javascript
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: 页面卸载时清除

javascript
let timer = null;

function startTimer() {
    timer = setInterval(() => {
        console.log('定时器执行');
    }, 1000);
}

// 页面卸载时清除定时器
window.addEventListener('beforeunload', () => {
    if (timer) {
        clearInterval(timer);
    }
});

startTimer();

示例5: 切换暂停/继续

javascript
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() 方法在指定的延迟(毫秒)后执行一次函数或代码片段。

语法:

javascript
setTimeout(function, delay, arg1, arg2, ...)

// 或使用箭头函数
setTimeout(() => { /* 代码 */ }, delay)

// 函数名形式
setTimeout(functionName, delay)

参数说明:

  • function: 要执行的函数或代码字符串
  • delay: 延迟时间(毫秒),1秒 = 1000毫秒
  • arg1, arg2, ...: 传递给函数的参数

返回值: 定时器 ID(整数),用于清除定时器

6.4.2 基本用法示例

示例1: 延迟执行

javascript
// 1秒后执行
setTimeout(() => {
    console.log('1秒后执行');
}, 1000);

示例2: 使用函数名

javascript
function showMessage() {
    console.log('延迟执行');
}

setTimeout(showMessage, 2000);

示例3: 传递参数

javascript
function greet(name, age) {
    console.log(`Hello, ${name}! 你今年${age}岁。`);
}

setTimeout(greet, 2000, '张三', 18);

示例4: 立即执行

javascript
// 延迟0毫秒,实际上会尽快执行(在当前代码执行完后)
setTimeout(() => {
    console.log('尽快执行');
}, 0);

6.4.3 实际应用场景

场景1: 延迟提示消失

javascript
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)

javascript
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)

javascript
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: 异步加载

javascript
function loadContent() {
    const content = document.getElementById('content');
    content.textContent = '加载中...';
    
    // 模拟异步加载
    setTimeout(() => {
        content.textContent = '内容加载完成!';
    }, 2000);
}

loadContent();

场景5: 动画延迟

javascript
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() 创建的定时器。

语法:

javascript
clearTimeout(timerId)

参数说明:

  • timerId: 由 setTimeout() 返回的定时器 ID

6.5.2 基本用法

示例1: 清除定时器

javascript
const timer = setTimeout(() => {
    console.log('这行不会执行');
}, 3000);

// 1秒后清除定时器
setTimeout(() => {
    clearTimeout(timer);
    console.log('定时器已清除');
}, 1000);

示例2: 按钮取消操作

javascript
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: 防抖中的清除

javascript
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 时间精度问题

问题: 定时器的延迟时间不是精确的

javascript
console.log('开始:', Date.now());

setTimeout(() => {
    console.log('实际延迟:', Date.now() - startTime);
}, 1000);

const startTime = Date.now();
// 实际延迟可能大于1000毫秒

原因:

  • 浏览器的最小时间间隔限制(通常为4ms)
  • 主线程繁忙时会延迟执行
  • 页面不可见时降低频率

解决方案:

javascript
// 使用 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 可能指向不正确

javascript
const obj = {
    name: '张三',
    sayName: function() {
        setTimeout(function() {
            console.log(this.name); // undefined (严格模式) 或 window.name
        }, 1000);
    }
};

obj.sayName();

解决方案1: 使用箭头函数

javascript
const obj = {
    name: '张三',
    sayName: function() {
        setTimeout(() => {
            console.log(this.name); // '张三'
        }, 1000);
    }
};

解决方案2: 使用 bind

javascript
const obj = {
    name: '张三',
    sayName: function() {
        setTimeout(function() {
            console.log(this.name); // '张三'
        }.bind(this), 1000);
    }
};

解决方案3: 保存 this

javascript
const obj = {
    name: '张三',
    sayName: function() {
        const self = this;
        setTimeout(function() {
            console.log(self.name); // '张三'
        }, 1000);
    }
};

6.6.3 内存泄漏问题

问题: 定时器未被清除可能导致内存泄漏

javascript
// ❌ 错误示例
function startTimer() {
    setInterval(() => {
        console.log('定时器运行');
    }, 1000);
    // 函数执行完,但定时器仍在运行
}

startTimer();
// 如果多次调用 startTimer(),会创建多个定时器

解决方案:

javascript
// ✅ 正确示例
let timer = null;

function startTimer() {
    // 清除之前的定时器
    if (timer) {
        clearInterval(timer);
    }
    
    // 创建新定时器
    timer = setInterval(() => {
        console.log('定时器运行');
    }, 1000);
}

// 页面卸载时清除
window.addEventListener('beforeunload', () => {
    if (timer) {
        clearInterval(timer);
    }
});

6.6.4 页面可见性

问题: 页面不可见时定时器仍在运行

解决方案:

javascript
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

javascript
// ✅ 好的做法: 保存定时器 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 清除之前的定时器

javascript
// ✅ 好的做法: 清除之前的定时器
let timer = null;

function restartTimer() {
    if (timer) {
        clearInterval(timer);
    }
    
    timer = setInterval(() => {
        console.log('新的定时器');
    }, 1000);
}

6.7.3 页面卸载时清除定时器

javascript
let timer = null;

function startTimer() {
    timer = setInterval(() => {
        console.log('定时器运行');
    }, 1000);
}

// 页面卸载时清除
window.addEventListener('beforeunload', () => {
    if (timer) {
        clearInterval(timer);
    }
});

startTimer();

6.7.4 使用 requestAnimationFrame 替代定时器做动画

javascript
// ❌ 不好的做法: 使用 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 秒表计时器

javascript
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 自动保存

javascript
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 轮询等待

javascript
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 进度条动画

javascript
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 自动滚动

javascript
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 总结对比表

特性setIntervalsetTimeout
执行次数重复执行执行一次
清除方法clearIntervalclearTimeout
返回值定时器 ID定时器 ID
适用场景定时任务、轮询、动画延迟执行、防抖、异步操作

6.10 常见问题

6.10.1 setInterval 和 setTimeout 的区别是什么?

答案:

  • setInterval: 重复执行,需要用 clearInterval 清除
  • setTimeout: 执行一次,需要用 clearTimeout 清除
javascript
// setInterval: 重复执行
setInterval(() => console.log('重复'), 1000);

// setTimeout: 执行一次
setTimeout(() => console.log('一次'), 1000);

6.10.2 如何停止定时器?

答案: 保存定时器 ID,然后使用对应的清除方法。

javascript
// 停止 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 事件中手动清除。

javascript
window.addEventListener('beforeunload', () => {
    if (timer) {
        clearInterval(timer);
    }
});

6.10.5 如何实现防抖和节流?

答案:

javascript
// 防抖
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;
        }
    };
}

Released under the MIT License.