提示:这是一个非常弱智的前端学习者的一点追求弱智简单清晰的傻瓜笔记,一个经常打完代码打辩论的某弗雷尔卓德寒冷211高校的天天想家的孩子的“胡说八道”。
这是第四节——Web API总结与深化进阶
关键字 | 特点 | 使用场景 | 注意事项 |
---|---|---|---|
var | 函数作用域,可重复声明 | 旧版代码兼容 | 存在变量提升,建议淘汰 |
let | 块级作用域,不可重复声明 | 需要重新赋值的变量 | 无变量提升,需先声明后使用 |
const | 块级作用域,声明后不可重新赋值 | 常量、数组、对象声明 | 声明时必须初始化,对引用类型可修改内部属性 |
// const 使用示例
const PI = 3.14;
const colors = ['red', 'blue'];
colors.push('green'); // 允许操作数组元素
1.JavaScript 的变量提升(Hoisting)是理解代码执行顺序和作用域的关键概念。变量提升的本质在于 JavaScript 引擎在代码执行前,会先进行编译阶段,在此阶段中所有变量和函数声明会被“提升”到其作用域的顶部4。
声明
被提升,而初始化操作(赋值)
仍然留在原地。例如:console.log(a); // 输出 undefined
var a = 10;
上述代码的实际执行顺序如下:
var a; // 声明被提升
console.log(a); // 输出 undefined,因为此时 a 尚未被赋值
a = 10; // 初始化操作
foo(); // 输出 1
function foo() {
console.log(1);
}
上述代码的实际执行顺序如下:
function foo() {
console.log(1);
}
foo(); // 输出 1
bar(); // 抛出 TypeError: bar is not a function
var bar = function() {
console.log(2);
};
上述代码的实际执行顺序如下:
var bar; // 声明被提升
bar(); // 此时 bar 为 undefined,因此抛出错误
bar = function() {
console.log(2);
};
let
和 const
的引入改变了变量提升的行为。虽然它们也会在编译阶段被解析,但它们不会被提升到作用域顶部。这种行为被称为“暂时性死区”(Temporal Dead Zone, TDZ),即在变量声明之前访问它们会导致 ReferenceError。console.log(x); // 抛出 ReferenceError: x is not defined
let x = 10;
console.log(y); // 抛出 ReferenceError: y is not defined
const y = 20;
function test() {
console.log(a); // 输出 undefined
var a = 10;
}
test();
上述代码的实际执行顺序如下:
function test() {
var a; // 声明被提升
console.log(a); // 输出 undefined
a = 10; // 初始化操作
}
test();
console.log(foo); // 输出 undefined
console.log(bar()); // 输出 1
console.log(baz()); // 抛出 TypeError: baz is not a function
var foo = 10;
function bar() {
return 1;
}
var baz = function() {
return 2;
};
实际执行顺序
var foo; // 声明被提升
var baz; // 声明被提升
function bar() {
return 1;
}
console.log(foo); // 输出 undefined
console.log(bar()); // 输出 1
console.log(baz()); // 抛出 TypeError: baz is not a function
foo = 10; // 初始化操作
baz = function() {
return 2;
};
TypeError
。const constantValue = 42;
constantValue = 100; // Uncaught TypeError: Assignment to constant variable.
需要注意的是,const 的不可重新赋值规则仅适用于变量的绑定本身,而不影响对象或数组的内容。如果 const 绑定的是一个引用类型
(如对象或数组),则可以修改其属性或元素,但不能重新分配整个引用。
const obj = { key: "value" };
obj.key = "newValue"; // 合法操作
console.log(obj.key); // 输出 "newValue"
obj = {}; // Uncaught TypeError: Assignment to constant variable.
const frozenObj = Object.freeze({ a: 1, b: { c: 2 } });
frozenObj.a = 10; // 无效操作,不会改变值
console.log(frozenObj.a); // 输出 1
frozenObj.b.c = 20; // 合法操作,因为嵌套对象未被冻结
console.log(frozenObj.b.c); // 输出 20
if (true) {
var varVariable = "Var";
let letVariable = "Let";
const constVariable = "Const";
}
console.log(varVariable); // 输出 "Var"
console.log(letVariable); // ReferenceError: letVariable is not defined
console.log(constVariable); // ReferenceError: constVariable is not defined
示例代码
以下是一个综合示例,展示 const 的块级作用域与不可重新赋值规则:
{
const blockScopedConst = "I'm block scoped!";
console.log(blockScopedConst); // 输出 "I'm block scoped!"
}
// console.log(blockScopedConst); // ReferenceError: blockScopedConst is not defined
const immutableValue = 100;
// immutableValue = 200; // Uncaught TypeError: Assignment to constant variable.
const mutableObject = { property: "mutable" };
mutableObject.property = "changed"; // 合法操作
console.log(mutableObject.property); // 输出 "changed"
// mutableObject = {}; // Uncaught TypeError: Assignment to constant variable.
1.1 作用与分类
1.2 DOM(文档对象模型)
1.3 DOM 树
1.4 DOM 对象
element.id
)。element.style.color = 'red'
)。querySelector
、createElement
)。方法 | 返回值 | 特点 |
---|---|---|
querySelector() | 首个匹配元素 | 支持CSS选择器 |
querySelectorAll() | NodeList伪数组 | 无数组方法(pop/push) |
getElementById() | 单个元素 | 仅通过ID获取 |
// 获取示例
const header = document.querySelector('#header');
const buttons = document.querySelectorAll('.btn');
console.log(buttons.length); // 伪数组长度
属性 | 特点 | 使用场景 |
---|---|---|
innerText |
纯文本(不解析HTML标签) | 防止XSS攻击、显示纯文本内容 |
innerHTML |
解析HTML标签(可动态生成DOM) | 插入复杂结构、渲染模板化内容 |
// 动态生成列表项
const listEl = document.querySelector('#todo-list');
const task = {id: 1, title: ' 完成笔记 '};
listEl.innerHTML += <li class="task" data-id="${task.id}">${task.title}</li>;
// 年会抽奖案例
const prizeEl = document.querySelector('#prize');
prizeEl.innerHTML = `${winnerName}`; // 解析标签
<br>// 评论区展示用户输入(安全场景)
<br>const commentEl = document.querySelector('.comment');
<br>const userInput = '用户输入:';
<br>commentEl.innerText = userInput; // 显示为纯文本,避免脚本执行
XSS防范:
innerText
(如评论、表单提交),避免innerHTML
直接插入未过滤的内容。// 危险操作(可能注入脚本)
const riskyInput = '';
element.innerHTML = riskyInput; // 执行恶意脚本
// 安全操作
element.innerText = riskyInput; // 显示为纯文本
性能优化:
innerHTML
会触发回流,建议批量操作或使用文档碎片(DocumentFragment
)。// 低效操作(多次修改innerHTML)
for (let i = 0; i < 100; i++) {
listEl.innerHTML += `${i}
混合场景应用:
const post = {
title: 'Web API进阶',
content: '本文介绍了DOM操作与事件系统'
};
const postEl = document.createElement('article');
postEl.innerHTML = `
${post.title}
${post.content}
`;
// 安全处理用户评论
const comment = '很实用的内容!';
postEl.querySelector('.comments').innerText = comment;
方法 | 特点 | 使用场景 |
---|---|---|
style 属性 |
行内样式(优先级最高) | 动态修改单一样式、临时覆盖样式 |
className |
覆盖元素所有类名 | 整体切换样式集合(如主题切换) |
classList |
类名精确增删改(支持链式操作) | 精细控制样式类、避免覆盖其他类 |
示例
//1.按钮悬停高亮效果
// 获取按钮元素
const btn = document.querySelector('.btn');
// 鼠标进入事件:添加高亮样式与缩放效果
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = 'rgba(0, 123, 255, 0.2)'; // 半透明蓝色背景
btn.style.transform = 'scale(1.05)'; // 1.05倍缩放
});
// 鼠标离开事件:清除高亮样式与缩放效果
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = ''; // 清空背景色
btn.style.transform = ''; // 清空变换效果
});
// 2.表单验证错误提示
// 获取输入框元素
const input = document.querySelector('#username');
// 验证输入长度(小于3字符时触发错误提示)
if (input.value.length < 3) {
input.style.borderColor = 'red'; // 红色边框
input.style.boxShadow = '0 0 5px rgba(255, 0, 0, 0.3)'; // 红色阴影
}
// 优化:添加输入时实时验证(清除错误提示)
input.addEventListener('input', () => {
if (input.value.length >= 3) {
input.style.borderColor = ''; // 清空边框色
input.style.boxShadow = ''; // 清空阴影
}
});
//1.页面加载时切换响应式布局
// 监听窗口加载完成事件
window.addEventListener('load', () => {
// 判断窗口宽度是否小于768px(移动端阈值)
const isMobile = window.innerWidth < 768;
// 根据设备类型切换body的类名
document.body.className = isMobile
? 'mobile-layout' // 移动端布局类
: 'desktop-layout'; // 桌面端布局类
});
// 优化:添加窗口Resize事件,实时响应屏幕变化
window.addEventListener('resize', () => {
const isMobile = window.innerWidth < 768;
document.body.className = isMobile ? 'mobile-layout' : 'desktop-layout';
});
//2. 游戏角色状态切换
// 获取角色元素
const playerEl = document.querySelector('.player');
// 定义角色受伤状态(true为受伤,false为正常)
const isDamaged = true;
// 根据状态切换角色类名
playerEl.className = isDamaged
? 'player damaged shaking' // 受伤状态(添加受伤和抖动类)
: 'player'; // 正常状态
// 进阶:使用classList实现更安全的类名操作
if (isDamaged) {
playerEl.classList.add('damaged', 'shaking'); // 添加受伤样式
} else {
playerEl.classList.remove('damaged', 'shaking'); // 移除受伤样式
}
//1. 导航栏滚动吸顶效果
// 获取导航栏元素
const navEl = document.querySelector('nav');
// 监听窗口滚动事件
window.addEventListener('scroll', () => {
// 当滚动距离超过100px时
if (window.scrollY > 100) {
navEl.classList.add('sticky', 'shadow-lg'); // 添加吸顶和阴影效果
} else {
navEl.classList.remove('sticky', 'shadow-lg'); // 移除吸顶和阴影效果
}
});
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用节流优化滚动监听
window.addEventListener('scroll', throttle(() => {
// 吸顶逻辑
}, 150)); // 每150ms执行一次
//2. 购物车商品选中状态切换
// 获取购物车商品元素
const itemEl = document.querySelector('.cart-item');
// 监听点击事件
itemEl.addEventListener('click', () => {
// 切换选中状态相关样式
itemEl.classList.toggle('selected'); // 选中状态标识
itemEl.classList.toggle('bg-blue-50'); // 浅蓝色背景
itemEl.classList.toggle('border-blue-500'); // 蓝色边框
});
// 优化:批量操作classList(减少DOM操作次数)
itemEl.addEventListener('click', () => {
const isSelected = itemEl.classList.toggle('selected');
// 根据选中状态一次性添加/移除多个类
if (isSelected) {
itemEl.classList.add('bg-blue-50', 'border-blue-500');
} else {
itemEl.classList.remove('bg-blue-50', 'border-blue-500');
}
});
// 进阶:多商品购物车实现(使用事件委托)
const cartEl = document.querySelector('.cart');
cartEl.addEventListener('click', (e) => {
const item = e.target.closest('.cart-item');
if (item) {
// 切换选中状态
item.classList.toggle('selected');
item.classList.toggle('bg-blue-50');
item.classList.toggle('border-blue-500');
}
});
CSS变量与JS结合
style
操作CSS变量,实现主题色动态切换(比修改类名更灵活)。// 定义CSS变量
:root {
--primary-color: #3498db;
--bg-color: #f5f7fa;
}
// JS动态修改
const themeBtn = document.querySelector('#theme-btn');
themeBtn.addEventListener('click', () => {
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
document.documentElement.style.setProperty('--bg-color', '#2c3e50');
});
类名批量操作技巧
classList
的add/remove/toggle
替代className
(避免覆盖其他类)。// 错误示范(覆盖所有类名)
element.className = 'active'; // 移除原有类名
// 正确示范(保留原有类名)
element.classList.add('active'); // 新增类名
动画性能优化
requestAnimationFrame
结合classList
触发CSS动画(减少重绘回流)。// 元素淡入效果
const fadeInElement = (el) => {
el.classList.add('opacity-0'); // 初始隐藏
requestAnimationFrame(() => {
el.classList.remove('opacity-0');
el.classList.add('opacity-100', 'transition-opacity', 'duration-500');
});
};
响应式样式控制
classList
实现响应式布局切换。const handleResponsive = () => {
const mainEl = document.querySelector('main');
if (window.innerWidth < 640) {
mainEl.classList.add('mobile-column');
mainEl.classList.remove('desktop-grid');
} else {
mainEl.classList.add('desktop-grid');
mainEl.classList.remove('mobile-column');
}
};
window.addEventListener('resize', handleResponsive);
handleResponsive(); // 初始化调用
方法 | 特点 | 示例 |
---|---|---|
style 属性 | 行内样式优先级最高 | el.style.color = ‘red’ |
className | 覆盖全部类名 | el.className = ‘active’ |
classList | 类名精确控制 | el.classList.toggle(‘dark-mode’) |
// 主题切换案例
themeBtn.addEventListener('click', () => {
container.classList.toggle('dark-theme');
});
方式 | 语法 | 特点 | 适用场景 |
---|---|---|---|
DOM L0 | element.onclick = function() {} |
- 简单直接 - 事件覆盖(重复绑定会覆盖前一个) - 仅支持冒泡阶段 |
- 简单交互场景 - 旧版浏览器兼容(如IE8以下) - 不需要事件捕获时 |
DOM L2 | element.addEventListener('click', handler) |
- 支持多监听器(可绑定多个回调) - 支持事件捕获/冒泡阶段 - 可移除监听器( removeEventListener ) |
- 复杂交互场景 - 需要事件委托时 - 需要精确控制事件流时 |
// 推荐方式
button.addEventListener('click', handleClick);
代码示例:
// DOM L0 (事件覆盖)
button.onclick = function() { console.log('点击1'); };
button.onclick = function() { console.log('点击2'); }; // 覆盖前一个,仅输出"点击2"
// DOM L2 (多监听器共存)
button.addEventListener('click', function() { console.log('点击A'); });
button.addEventListener('click', function() { console.log('点击B'); }); // 两个回调都会执行
移除监听器注意事项:
// 必须使用相同的函数引用才能移除
const handler = () => console.log('点击');
button.addEventListener('click', handler);
button.removeEventListener('click', handler); // 有效
// 箭头函数/匿名函数无法直接移除
button.addEventListener('click', () => console.log('点击'));
button.removeEventListener('click', () => console.log('点击')); // 无效!
核心属性:
属性 | 描述 | 示例 |
---|---|---|
e.target |
事件触发的实际元素(最深层的目标元素) | e.target.value (获取表单元素值) |
e.currentTarget |
当前监听事件的元素(可能是父元素,取决于事件委托) | e.currentTarget === this (在传统函数中) |
e.type |
事件类型(如'click' 、'keydown' ) |
if (e.type === 'submit') { ... } |
e.key |
键盘事件的按键值(如'Enter' 、'Escape' ) |
if (e.key === 'Enter') { submitForm(); } |
e.preventDefault() |
阻止默认行为(如表单提交、链接跳转) | form.addEventListener('submit', e => e.preventDefault()); |
e.stopPropagation() |
阻止事件冒泡/捕获(事件不再传播到其他元素) | childEl.addEventListener('click', e => e.stopPropagation()); |
实战案例:
// 表单提交拦截
form.addEventListener('submit', (e) => {
e.preventDefault(); // 阻止表单默认提交
const formData = new FormData(form);
console.log('表单数据:', Object.fromEntries(formData.entries()));
});
// 键盘快捷键监听
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') { // Ctrl+S
e.preventDefault(); // 阻止浏览器默认的"保存页面"行为
saveDocument();
}
});
阶段 | 触发顺序 | 控制方法 |
---|---|---|
捕获 | 父元素 → 子元素 | addEventListener(…, true) |
冒泡(默认) | 子元素 → 父元素 | e.stopPropagation() |
事件流三个阶段:
window
→document
→…→目标元素(由外向内)document
→window
(由内向外,默认阶段)控制事件流:
// 1. 捕获阶段监听(第三个参数为true)
parentEl.addEventListener('click', (e) => {
console.log('捕获阶段触发');
}, true);
// 2. 冒泡阶段监听(默认,第三个参数为false或省略)
parentEl.addEventListener('click', (e) => {
console.log('冒泡阶段触发');
});
// 3. 阻止事件传播
childEl.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止事件继续传播(捕获或冒泡)
console.log('事件被拦截,不会触发父元素监听器');
});
// 4. 阻止默认行为(但不影响事件传播)
linkEl.addEventListener('click', (e) => {
e.preventDefault(); // 阻止链接跳转
console.log('链接被点击,但不会跳转');
});
事件委托经典应用:
<ul id="todo-list">
<li data-id="1">完成作业li>
<li data-id="2">购买 groceriesli>
ul>
// 在父元素上监听,处理所有子元素的点击
const todoList = document.getElementById('todo-list');
todoList.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') { // 只处理li元素的点击
const taskId = e.target.dataset.id;
console.log('点击任务:', taskId);
toggleTaskCompletion(taskId);
}
});
性能优化建议:
stopPropagation()
可能导致其他功能失效(如全局点击遮罩关闭模态框)。focusin
/focusout
等不冒泡的事件常见误区:
e.stopPropagation()
≠ e.preventDefault()
preventDefault()
会阻止跳转,但事件仍会冒泡通过合理利用事件流机制,可实现更高效、更灵活的交互逻辑,尤其在处理动态内容和复杂UI时尤为重要。
类型 | 特点 | 清除方法 |
---|---|---|
setInterval() | 周期性执行 | clearInterval(timer) |
setTimeout() | 单次延迟执行 | clearTimeout(timer) |
// 倒计时示例
const timer = setInterval(() => {
if(timeLeft <= 0) clearInterval(timer);
}, 1000);
// 时间戳获取
const timestamp = +new Date();
// 日期格式化
const now = new Date();
console.log(`${now.getFullYear()}-${now.getMonth()+1}`);
// 1. 时间戳获取(三种方式)
const timestamp1 = new Date().getTime(); // 推荐:明确调用getTime()
const timestamp2 = +new Date(); // 简写:将Date对象转为数字
const timestamp3 = Date.now(); // ES5新增:最简洁的方式
console.log(`时间戳:${timestamp3}`); // 输出类似:1686556800000
// 2. 日期格式化(原生方法)
const now = new Date();
const year = now.getFullYear(); // 四位年份:2025
const month = now.getMonth() + 1; // 月份(0-11,需+1):6
const day = now.getDate(); // 日期:12
const hour = now.getHours(); // 小时(0-23):14
const minute = now.getMinutes(); // 分钟:30
const second = now.getSeconds(); // 秒:45
// 组合格式化字符串
console.log(`${year}-${month.toString().padStart(2, '0')}`); // 2025-06
console.log(`${hour}:${minute.toString().padStart(2, '0')}`); // 14:30
// 3. 日期计算(倒计时案例)
const targetDate = new Date('2025-12-31 23:59:59');
const nowTime = new Date();
const diff = targetDate - nowTime; // 剩余毫秒数
// 转换为天/时/分/秒
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
console.log(`距离年底还有:${days}天${hours}时${minutes}分${seconds}秒`);
时区转换
// 本地时间转UTC时间
const localDate = new Date();
const utcDate = new Date(localDate.toUTCString());
console.log(`本地时间:${localDate},UTC时间:${utcDate}`);
// 时区偏移量(分钟)
const offsetMinutes = localDate.getTimezoneOffset();
console.log(`时区偏移:${offsetMinutes}分钟(东八区为-480)`);
日期格式化库推荐
// 安装:npm install dayjs
import dayjs from 'dayjs';
// 格式化
dayjs().format('YYYY-MM-DD HH:mm:ss'); // 2025-06-12 14:30:45
dayjs('2025-06-12').fromNow(); // '1小时前'
dayjs().add(7, 'day').format(); // 一周后日期
时间差计算
// 计算两个日期的差值(天/月/年)
function getDateDiff(start, end, unit) {
const diffMs = end - start;
const diffMap = {
'day': diffMs / (1000 * 60 * 60 * 24),
'hour': diffMs / (1000 * 60 * 60),
'minute': diffMs / (1000 * 60),
'second': diffMs / 1000
};
return Math.floor(diffMap[unit] || 0);
}
const birthDate = new Date('1990-01-01');
const ageDays = getDateDiff(birthDate, now, 'day');
console.log(`已出生 ${ageDays} 天`);
月份取值误区
getMonth()
返回0-11(1月为0),需始终+1
:// 错误:直接使用getMonth()
console.log(`当前月份:${now.getMonth()}`); // 输出5(6月实际为5)
// 正确:+1后使用
console.log(`当前月份:${now.getMonth() + 1}`); // 输出6
跨浏览器兼容性
Date('YYYY-MM-DD')
格式,推荐使用Date(year, month, day)
:// 兼容写法
const date = new Date(2025, 5, 12); // 2025年6月12日(month=5)
性能优化
Date
对象(如定时器中),可缓存实例:// 低效写法
setInterval(() => {
const now = new Date(); // 每次触发都创建新对象
console.log(now);
}, 1000);
// 优化写法
let now = new Date();
setInterval(() => {
now = new Date(now.getTime() + 1000); // 基于旧时间戳更新
console.log(now);
}, 1000);
<div id="countdown">距离活动开始还有:<span id="days">0span>天div>
// 目标时间(活动开始时间)
const targetTime = new Date('2025-07-01 00:00:00');
const countdownEl = document.getElementById('countdown');
const daysEl = document.getElementById('days');
function updateCountdown() {
const now = new Date();
const diff = targetTime - now;
if (diff <= 0) {
countdownEl.innerHTML = '活动已开始!';
return;
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
daysEl.textContent = days;
}
// 初始化调用
updateCountdown();
// 每分钟更新一次(比每秒更新更省性能)
setInterval(updateCountdown, 60 * 1000);
通过合理使用日期对象及其API,可实现时间戳转换、倒计时、日期计算等功能,结合第三方库(如dayjs)能进一步提升开发效率和兼容性。
类型 | 生命周期 | 作用域 | 容量 | 自动发送 | 访问权限 |
---|---|---|---|---|---|
localStorage | 永久存储 | 同源共享 | ~5MB | × | 同源 |
sessionStorage | 标签页关闭失效 | 单标签页 | ~5MB | × | 同源+同标签页 |
Cookie | 可设过期时间 | 同源共享 | ~4KB | √ | 同源 |
// 复杂数据存储
const user = { name: 'Alice', id: 101 };
localStorage.setItem('user', JSON.stringify(user));
// 数据读取
const data = JSON.parse(localStorage.getItem('user'));
概念 | 触发条件 | 性能影响 |
---|---|---|
回流 | 尺寸/布局变化(如窗口缩放) | 高开销(触发重新布局) |
重绘 | 颜色等视觉变化(如背景色) | 低开销(不改变布局) |
优化建议:
// 避免频繁操作样式
const el = document.getElementById('box');
el.style.display = 'none'; // 触发回流
// 批量修改
el.classList.add('hidden'); // 减少操作次数
事件 | 触发时机 |
---|---|
touchstart | 手指触摸屏幕 |
touchmove | 手指在屏幕滑动 |
touchend | 手指离开屏幕 |
// 滑动检测
slider.addEventListener('touchmove', (e) => {
console.log(e.touches[0].clientX);
});
// 节点操作
const newNode = document.createElement('div');
parent.appendChild(newNode);
parent.insertBefore(newNode, existingChild);
parent.removeChild(childNode);
// 属性操作
el.dataset.id; // 获取 data-id
el.getBoundingClientRect(); // 获取元素位置
// 常用数组转换
const names = ['Tom', 'Jerry'];
const newArr = names.map(name => `${name}先生`);
const str = names.join(','); // "Tom,Jerry"
// Tab栏切换案例
ul.addEventListener('click', (e) => {
if (e.target.tagName === 'A') {
// 导航切换
document.querySelector('.active').classList.remove('active');
e.target.classList.add('active');
// 内容切换
const id = e.target.dataset.id;
document.querySelector('.item.active').classList.remove('active');
document.querySelector(`.item:nth-child(${id+1})`).classList.add('active');
}
});
事件类型 | 触发时机 | 使用场景 |
---|---|---|
load | 所有资源加载完成 | 页面初始化 |
DOMContentLoaded | DOM树构建完成 | 提前操作DOM |
scroll | 页面滚动时 | 吸顶导航/返回顶部 |
resize | 窗口大小变化 | 响应式布局 |
// 获取滚动位置
window.addEventListener('scroll', () => {
console.log('垂直滚动:', document.documentElement.scrollTop);
});
// 响应式布局检测
window.addEventListener('resize', () => {
console.log('当前宽度:', document.documentElement.clientWidth);
});
属性/方法 | 描述 | 是否包含边框/滚动条 |
---|---|---|
clientWidth | 可视区域宽 | × |
offsetWidth | 元素总宽 | √ |
getBoundingClientRect() | 元素位置和尺寸 | √ |
// 获取元素位置
const rect = element.getBoundingClientRect();
console.log(`元素位置:左${rect.left} 上${rect.top} 宽${rect.width}`);
// 父子节点
const parent = child.parentNode;
const children = parent.children; // 仅元素节点
// 兄弟节点
const next = node.nextElementSibling;
const prev = node.previousElementSibling;
// 创建节点
const newDiv = document.createElement('div');
// 添加节点
parent.appendChild(newDiv); // 末尾添加
parent.insertBefore(newDiv, existingChild); // 指定位置
// 克隆节点
const cloned = original.cloneNode(true); // 深度克隆
// 删除节点
parent.removeChild(childToRemove);
关系类型 | 属性/方法 | 特点 | 示例 |
---|---|---|---|
父节点 | node.parentNode |
- 返回DOM节点的父节点(可能是元素节点或文本节点) | const parent = el.parentNode; |
子节点集合 | node.children |
- 仅返回元素节点(HTMLElement 类型)- 返回实时集合(动态更新) |
const children = parent.children; |
首个/末个子元素 | node.firstElementChild node.lastElementChild |
- 仅返回元素节点 - 不存在时返回 null |
const firstChild = parent.firstElementChild; |
兄弟节点 | node.nextElementSibling node.previousElementSibling |
- 仅返回元素节点 - 不存在时返回 null |
const nextSibling = el.nextElementSibling; |
所有子节点 | node.childNodes |
- 返回所有类型节点(包括文本节点、注释节点) - 返回实时集合 |
const allNodes = parent.childNodes; |
节点关系实战:
// 示例HTML结构
<div id="parent">
<p>文本1</p>
<div>元素1</div>
<!-- 注释 -->
<span>文本2</span>
</div>
// 1. 获取所有子元素(不含文本节点)
const parent = document.getElementById('parent');
const elementChildren = Array.from(parent.children); // [p, div, span]
// 2. 获取所有子节点(含文本节点、注释)
const allChildren = Array.from(parent.childNodes); // [文本节点, p, 文本节点, div, 注释, 文本节点, span]
// 3. 遍历兄弟节点
let current = parent.firstElementChild;
while (current) {
console.log(current.tagName); // P → DIV → SPAN
current = current.nextElementSibling;
}
操作类型 | 方法 | 特点 | 示例 |
---|---|---|---|
创建节点 | document.createElement(tagName) |
- 创建元素节点 - 返回 HTMLElement 对象 |
const div = document.createElement('div'); |
添加节点 | parent.appendChild(node) parent.insertBefore(newNode, referenceNode) |
- appendChild :添加到末尾- insertBefore :插入到参考节点前 |
parent.appendChild(div); parent.insertBefore(div, firstChild); |
替换节点 | parent.replaceChild(newNode, oldNode) |
- 用新节点替换旧节点 | parent.replaceChild(newDiv, oldDiv); |
克隆节点 | node.cloneNode(deep) |
- deep=true :深克隆(包含所有子节点)- deep=false :浅克隆(仅复制节点本身) |
const clone = original.cloneNode(true); |
删除节点 | parent.removeChild(node) node.remove() |
- removeChild :需要通过父节点删除- remove() :直接删除自身(IE不支持) |
parent.removeChild(child); child.remove(); |
批量操作 | DocumentFragment |
- 轻量级容器 - 操作不触发DOM重绘 - 最终插入时才渲染 |
const frag = document.createDocumentFragment(); frag.appendChild(div); parent.appendChild(frag); |
节点操作实战:
// 1. 创建并添加节点(带属性)
const newDiv = document.createElement('div');
newDiv.id = 'new-element';
newDiv.className = 'highlight';
newDiv.textContent = '新内容';
parent.appendChild(newDiv);
// 2. 插入到指定位置
const referenceNode = parent.querySelector('p');
parent.insertBefore(newDiv, referenceNode); // 插入到p元素前
// 3. 深克隆与浅克隆对比
const original = document.querySelector('#original');
// 深克隆(包含所有子节点)
const deepClone = original.cloneNode(true);
// 浅克隆(仅复制节点本身)
const shallowClone = original.cloneNode(false);
// 4. 批量添加节点(使用DocumentFragment优化性能)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment); // 仅触发一次DOM重绘
// 5. 动态删除节点
const itemToRemove = list.querySelector('li:first-child');
list.removeChild(itemToRemove); // 方法1:通过父节点删除
itemToRemove.remove(); // 方法2:直接删除(IE不支持)
避免频繁DOM操作
DocumentFragment
批量处理:// 低效:每次循环触发一次重绘
for (let i = 0; i < 100; i++) {
list.appendChild(document.createElement('li'));
}
// 高效:批量操作后一次性重绘
const frag = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
frag.appendChild(document.createElement('li'));
}
list.appendChild(frag);
克隆节点的事件绑定
cloneNode()
不会复制事件监听器(需手动重新绑定):original.addEventListener('click', handleClick);
const clone = original.cloneNode(true);
clone.addEventListener('click', handleClick); // 需重新绑定
动态节点的事件监听
// 监听父元素,处理所有子元素的点击
parent.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
// 处理按钮点击
}
});
案例1:动态表单字段
// 添加新字段按钮
const addBtn = document.getElementById('add-field');
const form = document.getElementById('my-form');
addBtn.addEventListener('click', () => {
const newInput = document.createElement('input');
newInput.type = 'text';
newInput.name = `field-${form.children.length}`;
form.appendChild(newInput);
});
案例2:列表项动态删除
const list = document.getElementById('item-list');
list.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
const item = e.target.closest('li');
list.removeChild(item); // 或 item.remove();
}
});
通过合理使用节点关系和操作方法,可实现复杂的DOM动态交互,同时注意性能优化和事件监听的正确处理。
类型 | 执行特点 | 清除方法 |
---|---|---|
setInterval | 重复执行 | clearInterval |
setTimeout | 单次执行 | clearTimeout |
// URL操作
location.href = 'https://new.com'; // 跳转
location.reload(true); // 强制刷新
// 浏览器检测
if(navigator.userAgent.includes('Mobile')) {
console.log('移动端访问');
}
// 历史记录
history.back(); // 后退
history.go(-1); // 后退一页
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1div>
<div class="swiper-slide">Slide 2div>
div>
div>
<script>
new Swiper('.swiper', {
loop: true,
pagination: { el: '.swiper-pagination' }
});
script>
// 异步回调示例
function fetchData(callback) {
setTimeout(() => {
callback('数据加载完成');
}, 1000);
}
fetchData((result) => {
console.log(result);
});
// 密码可见性切换
eyeIcon.addEventListener('click', () => {
passwordInput.type = passwordInput.type === 'password'
? 'text'
: 'password';
});
button.addEventListener('click', function() {
console.log(this); // 指向button元素
});
const obj = {
value: 10,
getValue() {
return this.value; // 指向obj
}
};
下面通过一个完整案例展示Web API的核心应用,包含DOM操作、事件处理、定时器、本地存储等知识点。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TaskFlow - 任务管理系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet">
<!-- Tailwind配置 -->
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#165DFF',
success: '#36D399',
danger: '#F87272',
warning: '#FBBD23',
dark: {
bg: '#1E293B',
card: '#334155',
text: '#F8FAFC'
}
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
},
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.task-transition {
transition: all 0.3s ease;
}
.card-shadow {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.dark-card-shadow {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1);
}
}
</style>
</head>
<body class="font-inter bg-gray-50 dark:bg-dark-bg text-gray-800 dark:text-dark-text min-h-screen transition-colors duration-300">
<!-- 顶部导航栏 -->
<header class="bg-white dark:bg-dark-card sticky top-0 z-10 card-shadow dark:card-shadow transition-all duration-300">
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
<div class="flex items-center space-x-2">
<i class="fa fa-tasks text-primary text-2xl"></i>
<h1 class="text-xl font-bold">TaskFlow</h1>
</div>
<div class="flex items-center space-x-4">
<!-- 主题切换按钮 -->
<button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<i class="fa fa-moon-o dark:hidden text-gray-600"></i>
<i class="fa fa-sun-o hidden dark:inline-block text-yellow-300"></i>
</button>
<!-- 任务统计 -->
<div class="hidden md:flex items-center space-x-4">
<span id="total-tasks" class="text-sm bg-primary/10 text-primary px-3 py-1 rounded-full">
<i class="fa fa-list-ul mr-1"></i> 0 个任务
</span>
<span id="completed-tasks" class="text-sm bg-success/10 text-success px-3 py-1 rounded-full">
<i class="fa fa-check mr-1"></i> 0 个完成
</span>
</div>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-6">
<!-- 任务添加区 -->
<section class="mb-8">
<div class="bg-white dark:bg-dark-card rounded-xl p-5 card-shadow dark:card-shadow transition-all duration-300">
<h2 class="text-lg font-semibold mb-4 flex items-center">
<i class="fa fa-plus-circle text-primary mr-2"></i> 添加新任务
</h2>
<form id="task-form" class="flex flex-col sm:flex-row gap-3">
<input
type="text"
id="task-input"
placeholder="输入任务描述..."
class="flex-1 px-4 py-2 border border-gray-200 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary dark:bg-gray-700 dark:text-white outline-none transition-all"
required
>
<button
type="submit"
class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 active:scale-95 transition-all flex items-center justify-center"
>
<i class="fa fa-plus mr-2"></i> 添加
</button>
</form>
<!-- 过滤选项 -->
<div class="mt-4 flex flex-wrap gap-2">
<button id="filter-all" class="filter-btn px-3 py-1 bg-primary text-white rounded-full text-sm">
全部
</button>
<button id="filter-active" class="filter-btn px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
待办
</button>
<button id="filter-completed" class="filter-btn px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full text-sm hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
已完成
</button>
</div>
</div>
</section>
<!-- 任务列表 -->
<section>
<div class="bg-white dark:bg-dark-card rounded-xl p-5 card-shadow dark:card-shadow transition-all duration-300">
<h2 class="text-lg font-semibold mb-4 flex items-center">
<i class="fa fa-list text-primary mr-2"></i> 我的任务
<span id="empty-state" class="ml-2 text-sm text-gray-500 dark:text-gray-400">
暂无任务,添加一个吧!
</span>
</h2>
<ul id="task-list" class="space-y-3">
<!-- 任务项将通过JS动态添加 -->
</ul>
</div>
</section>
</main>
<!-- 页脚 -->
<footer class="mt-10 py-6 border-t border-gray-200 dark:border-gray-700">
<div class="container mx-auto px-4 text-center text-sm text-gray-500 dark:text-gray-400">
<p>TaskFlow © 2025 | 使用 Web API 构建</p>
<div class="mt-2 flex justify-center space-x-4">
<span><i class="fa fa-code text-primary"></i> JavaScript</span>
<span><i class="fa fa-paint-brush text-primary"></i> Tailwind CSS</span>
<span><i class="fa fa-database text-primary"></i> localStorage</span>
</div>
</div>
</footer>
<script>
// 任务数据
let tasks = [];
let currentFilter = 'all';
// DOM 元素
const taskForm = document.getElementById('task-form');
const taskInput = document.getElementById('task-input');
const taskList = document.getElementById('task-list');
const emptyState = document.getElementById('empty-state');
const totalTasksEl = document.getElementById('total-tasks');
const completedTasksEl = document.getElementById('completed-tasks');
const themeToggle = document.getElementById('theme-toggle');
const filterButtons = document.querySelectorAll('.filter-btn');
// 初始化
document.addEventListener('DOMContentLoaded', () => {
// 加载本地存储的任务
loadTasks();
// 应用保存的主题
applySavedTheme();
// 更新任务统计
updateTaskStats();
// 初始化动画效果
initAnimations();
});
// 初始化动画效果
function initAnimations() {
// 滚动时导航栏效果
window.addEventListener('scroll', () => {
const header = document.querySelector('header');
if (window.scrollY > 10) {
header.classList.add('py-2');
header.classList.remove('py-3');
} else {
header.classList.add('py-3');
header.classList.remove('py-2');
}
});
}
// 加载本地存储的任务
function loadTasks() {
const savedTasks = localStorage.getItem('tasks');
if (savedTasks) {
tasks = JSON.parse(savedTasks);
renderTasks();
}
}
// 保存任务到本地存储
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
// 渲染任务列表
function renderTasks() {
// 清空现有列表
taskList.innerHTML = '';
// 根据当前过滤条件筛选任务
const filteredTasks = tasks.filter(task => {
if (currentFilter === 'active') return !task.completed;
if (currentFilter === 'completed') return task.completed;
return true; // all
});
// 更新空状态显示
emptyState.style.display = filteredTasks.length === 0 ? 'inline' : 'none';
// 创建任务项
filteredTasks.forEach(task => {
const taskItem = createTaskElement(task);
taskList.appendChild(taskItem);
});
// 更新任务统计
updateTaskStats();
}
// 创建任务元素
function createTaskElement(task) {
const taskItem = document.createElement('li');
taskItem.className = `flex items-center p-3 rounded-lg border border-gray-100 dark:border-gray-700 bg-white dark:bg-dark-card task-transition ${task.completed ? 'bg-gray-50 dark:bg-gray-800' : ''}`;
taskItem.dataset.id = task.id;
// 任务内容
taskItem.innerHTML = `
${task.completed ? 'checked' : ''}
>
${task.completed ? 'line-through text-gray-500 dark:text-gray-400' : ''}">
${task.text}
${formatDate(task.createdAt)}
`;
// 添加动画效果
setTimeout(() => {
taskItem.classList.add('opacity-100');
taskItem.style.transform = 'translateY(0)';
}, 10);
return taskItem;
}
// 格式化日期
function formatDate(timestamp) {
const date = new Date(timestamp);
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
// 更新任务统计
function updateTaskStats() {
const total = tasks.length;
const completed = tasks.filter(task => task.completed).length;
if (totalTasksEl) totalTasksEl.textContent = ` ${total} 个任务`;
if (completedTasksEl) completedTasksEl.textContent = ` ${completed} 个完成`;
}
// 切换任务完成状态
function toggleTaskCompletion(taskId) {
tasks = tasks.map(task =>
task.id === taskId ? { ...task, completed: !task.completed } : task
);
saveTasks();
renderTasks();
}
// 删除任务
function deleteTask(taskId) {
tasks = tasks.filter(task => task.id !== taskId);
saveTasks();
renderTasks();
}
// 应用保存的主题
function applySavedTheme() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
}
// 切换主题
function toggleTheme() {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
// 设置当前过滤条件
function setFilter(filter) {
currentFilter = filter;
// 更新按钮样式
filterButtons.forEach(btn => {
btn.classList.remove('bg-primary', 'text-white');
btn.classList.add('bg-gray-100', 'dark:bg-gray-700', 'text-gray-600', 'dark:text-gray-300');
});
const activeBtn = document.getElementById(`filter-${filter}`);
activeBtn.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'text-gray-600', 'dark:text-gray-300');
activeBtn.classList.add('bg-primary', 'text-white');
renderTasks();
}
// 事件监听器
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const taskText = taskInput.value.trim();
if (!taskText) return;
// 创建新任务
const newTask = {
id: Date.now().toString(),
text: taskText,
completed: false,
createdAt: Date.now()
};
tasks.unshift(newTask); // 添加到数组开头
saveTasks();
renderTasks();
// 清空输入框并聚焦
taskInput.value = '';
taskInput.focus();
});
taskList.addEventListener('change', (e) => {
if (e.target.classList.contains('task-checkbox')) {
const taskId = e.target.closest('li').dataset.id;
toggleTaskCompletion(taskId);
}
});
taskList.addEventListener('click', (e) => {
if (e.target.closest('.delete-btn')) {
const taskId = e.target.closest('li').dataset.id;
deleteTask(taskId);
}
});
themeToggle.addEventListener('click', toggleTheme);
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
const filter = btn.id.split('-')[1];
setFilter(filter);
});
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
// ESC 键清除输入框
if (e.key === 'Escape' && taskInput === document.activeElement) {
taskInput.value = '';
}
// Ctrl+D 切换主题
if (e.ctrlKey && e.key === 'd') {
toggleTheme();
}
});
</script>
</body>
</html>
document.createElement()
创建任务项appendChild()
将新任务添加到列表dataset
存储任务ID,classList
动态修改样式innerHTML
和textContent
更新文本和HTML结构Escape
清除输入框,Ctrl+D
切换主题preventDefault()
阻止表单默认提交localStorage
持久化任务数据requestAnimationFrame
优化性能(隐式应用于CSS过渡)DocumentFragment
批量操作DOM(示例中未显式使用,但通过innerHTML
清空再重建实现类似效果)@media
查询和Tailwind响应式类matchMedia
检测用户系统的主题偏好Date
对象处理任务创建时间的格式化Element.classList
实现主题切换的样式管理drag & drop API
)Notification API
)这个案例展示了如何综合运用Web API的各种功能构建一个完整的前端应用,涵盖了DOM操作、事件处理、浏览器存储、动画效果等核心知识点。