Web API总结与深化进阶
提示:这是一个非常弱智的前端学习者的一点追求弱智简单清晰的傻瓜笔记,一个经常打完代码打辩论的某弗雷尔卓德寒冷211高校的天天想家的孩子的“胡说八道”。
这是第四节——Web API总结与深化进阶
HHYYZZ的蓝旭笔记
Web API进阶
一、变量声明与Web API 基本认知
1、变量声明
// const 使用示例const PI = 3.14;const colors = [\'red\', \'blue\'];colors.push(\'green\'); // 允许操作数组元素
tips1 变量提升
1.JavaScript 的变量提升(Hoisting)是理解代码执行顺序和作用域的关键概念。变量提升的本质在于 JavaScript 引擎在代码执行前,会先进行编译阶段,在此阶段中所有变量和函数声明会被“提升”到其作用域的顶部4。
- 变量提升的基本原理
在 JavaScript 中,变量提升指的是变量或函数的声明会在物理代码运行之前被解释器处理并提升到当前作用域的顶部。需要注意的是,只有声明
被提升,而初始化操作(赋值)
仍然留在原地。例如:
console.log(a); // 输出 undefinedvar a = 10;
上述代码的实际执行顺序如下:
var a; // 声明被提升console.log(a); // 输出 undefined,因为此时 a 尚未被赋值a = 10; // 初始化操作
- 函数提升与变量提升的区别
JavaScript 中不仅有普通变量的提升,还有函数的提升。函数的提升分为两种情况:函数声明和函数表达式。
- 函数声明:函数声明会被完全提升,包括函数名和函数体。
foo(); // 输出 1function foo() { console.log(1);}
上述代码的实际执行顺序如下:
function foo() { console.log(1);}foo(); // 输出 1
- 函数表达式:函数表达式的变量名会被提升,但函数体不会被提升。
bar(); // 抛出 TypeError: bar is not a functionvar bar = function() { console.log(2);};
上述代码的实际执行顺序如下:
var bar; // 声明被提升bar(); // 此时 bar 为 undefined,因此抛出错误bar = function() { console.log(2);};
- 变量提升与let、const
从 ES6 开始,let
和const
的引入改变了变量提升的行为。虽然它们也会在编译阶段被解析,但它们不会被提升到作用域顶部。这种行为被称为“暂时性死区”(Temporal Dead Zone, TDZ),即在变量声明之前访问它们会导致 ReferenceError。
console.log(x); // 抛出 ReferenceError: x is not definedlet x = 10;console.log(y); // 抛出 ReferenceError: y is not definedconst y = 20;
- 全局变量与局部变量的提升
全局变量和局部变量的提升遵循相同的规则,但作用域不同。全局变量的作用域是整个脚本,而局部变量仅在其定义的函数内部有效。
function test() { console.log(a); // 输出 undefined var a = 10;}test();
上述代码的实际执行顺序如下:
function test() { var a; // 声明被提升 console.log(a); // 输出 undefined a = 10; // 初始化操作}test();
- 变量提升的实际应用与注意事项
- 在编写代码时,建议将变量声明放在作用域的顶部,以避免因变量提升导致的意外行为。
- 避免在声明之前使用变量,尤其是当使用 let 或 const 时,因为这会导致 ReferenceError。
- 理解函数声明与函数表达式之间的区别,以确保代码按预期执行。
示例代码
以下是一个综合示例,展示了变量提升和函数提升的行为:
console.log(foo); // 输出 undefinedconsole.log(bar()); // 输出 1console.log(baz()); // 抛出 TypeError: baz is not a functionvar foo = 10;function bar() { return 1;}var baz = function() { return 2;};
实际执行顺序
var foo; // 声明被提升var baz; // 声明被提升function bar() { return 1;}console.log(foo); // 输出 undefinedconsole.log(bar()); // 输出 1console.log(baz()); // 抛出 TypeError: baz is not a functionfoo = 10; // 初始化操作baz = function() { return 2;};
tips2 const不可重新赋值
- 不可重新赋值规则
const 声明的变量一旦初始化后,其绑定的值不能被重新赋值。如果尝试重新赋值,将抛出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 与对象的深层不可变性
尽管 const 防止了对变量绑定的重新赋值,但它并不能保证对象的深层不可变性。为了实现深层不可变性,可以结合使用 Object.freeze() 方法。然而,Object.freeze() 仅冻结对象的第一层属性,嵌套对象仍可被修改。
const frozenObj = Object.freeze({ a: 1, b: { c: 2 } });frozenObj.a = 10; // 无效操作,不会改变值console.log(frozenObj.a); // 输出 1frozenObj.b.c = 20; // 合法操作,因为嵌套对象未被冻结console.log(frozenObj.b.c); // 输出 20
- const 与其他声明方式的区别
与 var 的区别:var 没有块级作用域,且存在变量提升现象。const 则具有块级作用域,并且不存在变量提升,未初始化时会进入“暂时性死区”。
与 let 的区别:两者都具有块级作用域,但 const 必须初始化,且不能重新赋值,而 let 可以重新赋值。
if (true) { var varVariable = \"Var\"; let letVariable = \"Let\"; const constVariable = \"Const\";}console.log(varVariable); // 输出 \"Var\"console.log(letVariable); // ReferenceError: letVariable is not definedconsole.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 definedconst 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.
2、Web API 核心体系总结
1.1 作用与分类
- 作用:通过 JavaScript 操作 HTML 文档内容及浏览器环境。
- 分类:
- DOM(文档对象模型):专注于网页内容的操作与交互。
- BOM(浏览器对象模型):控制浏览器本身的功能(如窗口、地址栏、历史记录等)。
1.2 DOM(文档对象模型)
- 定义:浏览器提供的 API,用于呈现 HTML/XML 文档并实现交互操作。
- 核心功能:实现网页内容特效(如动态更新文本、样式)和用户交互(如点击事件响应)。
1.3 DOM 树
- 概念:将 HTML 文档解析为树状结构,直观展示标签间的层级关系(如父子、兄弟节点)。
- 作用:通过树状结构清晰描述网页内容的嵌套逻辑,便于开发者理解和操作节点关系。
1.4 DOM 对象
- 本质:浏览器根据 HTML 标签生成的 JavaScript 对象,映射标签的所有属性和行为。
- 特性:
- 标签属性可通过 DOM 对象直接访问(如
element.id
)。 - 修改 DOM 对象属性会实时同步到 HTML 标签(如
element.style.color = \'red\'
)。
- 标签属性可通过 DOM 对象直接访问(如
- 核心思想:将网页内容抽象为对象,通过操作对象实现对页面的动态控制。
- document 对象:DOM 的核心入口,包含网页所有内容,提供访问和操作页面元素的方法(如
querySelector
、createElement
)。
二、DOM 操作
1. 元素获取
// 获取示例const header = document.querySelector(\'#header\'); const buttons = document.querySelectorAll(\'.btn\'); console.log(buttons.length); // 伪数组长度
2. 内容操作
innerText
innerHTML
// 动态生成列表项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 = \'alert(\"攻击\")\';element.innerHTML = riskyInput; // 执行恶意脚本// 安全操作element.innerText = riskyInput; // 显示为纯文本
- 当内容包含用户输入时,优先使用
-
性能优化:
- 频繁使用
innerHTML
会触发回流,建议批量操作或使用文档碎片(DocumentFragment
)。
// 低效操作(多次修改innerHTML)for (let i = 0; i < 100; i++) { listEl.innerHTML += `${i}
`; // 每次触发回流}// 高效操作(批量插入)const fragment = document.createDocumentFragment();for (let i = 0; i < 100; i++) { const li = document.createElement(\'li\'); li.textContent = i; fragment.appendChild(li);}listEl.appendChild(fragment); // 仅一次回流
- 频繁使用
-
混合场景应用:
- 若需同时插入HTML和纯文本,可结合使用两者。
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;
3. 样式操作
3. 样式操作补充示例
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(); // 初始化调用
- 结合媒体查询与
// 主题切换案例themeBtn.addEventListener(\'click\', () => { container.classList.toggle(\'dark-theme\');});
三、事件处理解析与实践
1. 事件绑定对比
element.onclick = function() {}
- 事件覆盖(重复绑定会覆盖前一个)
- 仅支持冒泡阶段
- 旧版浏览器兼容(如IE8以下)
- 不需要事件捕获时
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(\'点击\')); // 无效!
2. 事件对象详解
核心属性:
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(); }});
3. 事件流
事件流三个阶段:
- 捕获阶段:从
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\">购买 groceries</li> </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时尤为重要。
四、定时器与日期
1. 定时器对比
// 倒计时示例const timer = setInterval(() => { if(timeLeft <= 0) clearInterval(timer);}, 1000);
2. 日期对象
// 时间戳获取const timestamp = +new Date(); // 日期格式化const now = new Date();console.log(`${now.getFullYear()}-${now.getMonth()+1}`);
3.详解与实践
核心操作示例
// 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(); // 四位年份:2025const month = now.getMonth() + 1; // 月份(0-11,需+1):6const day = now.getDate(); // 日期:12const hour = now.getHours(); // 小时(0-23):14const minute = now.getMinutes(); // 分钟:30const second = now.getSeconds(); // 秒:45// 组合格式化字符串console.log(`${year}-${month.toString().padStart(2, \'0\')}`); // 2025-06console.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)`);
-
日期格式化库推荐
- dayjs(轻量简洁):
// 安装:npm install dayjsimport dayjs from \'dayjs\';// 格式化dayjs().format(\'YYYY-MM-DD HH:mm:ss\'); // 2025-06-12 14:30:45dayjs(\'2025-06-12\').fromNow(); // \'1小时前\'dayjs().add(7, \'day\').format(); // 一周后日期
- dayjs(轻量简洁):
-
时间差计算
// 计算两个日期的差值(天/月/年)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\">0</span>天</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)能进一步提升开发效率和兼容性。
五、本地存储
存储方式对比
// 复杂数据存储const user = { name: \'Alice\', id: 101 };localStorage.setItem(\'user\', JSON.stringify(user));// 数据读取const data = JSON.parse(localStorage.getItem(\'user\'));
六、性能优化
重绘 vs 回流
优化建议:
// 避免频繁操作样式const el = document.getElementById(\'box\');el.style.display = \'none\'; // 触发回流// 批量修改el.classList.add(\'hidden\'); // 减少操作次数
七、移动端特性
触摸事件
// 滑动检测slider.addEventListener(\'touchmove\', (e) => { console.log(e.touches[0].clientX);});
附:核心API速查
DOM 操作
// 节点操作const newNode = document.createElement(\'div\');parent.appendChild(newNode);parent.insertBefore(newNode, existingChild);parent.removeChild(childNode);
// 属性操作el.dataset.id; // 获取 data-idel.getBoundingClientRect(); // 获取元素位置
数组方法
// 常用数组转换const names = [\'Tom\', \'Jerry\'];const newArr = names.map(name => `${name}先生`);const str = names.join(\',\'); // \"Tom,Jerry\"
八、事件进阶
1. 事件委托
- 原理:利用事件冒泡,在父元素处理子元素事件
- 优势:减少事件绑定数量,支持动态元素
// 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\'); }});
2. 页面事件
// 获取滚动位置window.addEventListener(\'scroll\', () => { console.log(\'垂直滚动:\', document.documentElement.scrollTop);});// 响应式布局检测window.addEventListener(\'resize\', () => { console.log(\'当前宽度:\', document.documentElement.clientWidth);});
3. 元素尺寸获取
// 获取元素位置const rect = element.getBoundingClientRect();console.log(`元素位置:左${rect.left} 上${rect.top} 宽${rect.width}`);
九、节点操作
1. 节点关系
// 父子节点const parent = child.parentNode;const children = parent.children; // 仅元素节点// 兄弟节点const next = node.nextElementSibling;const prev = node.previousElementSibling;
2. 节点操作
// 创建节点const newDiv = document.createElement(\'div\');// 添加节点parent.appendChild(newDiv); // 末尾添加parent.insertBefore(newDiv, existingChild); // 指定位置// 克隆节点const cloned = original.cloneNode(true); // 深度克隆// 删除节点parent.removeChild(childToRemove);
解析与实践
1. 节点关系操作
node.parentNode
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;}
2. 节点操作进阶
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操作
- 每次操作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);
- 每次操作DOM都会触发重绘/回流,推荐使用
-
克隆节点的事件绑定
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动态交互,同时注意性能优化和事件监听的正确处理。
十、BOM 对象
1. 定时器对比
2. 浏览器对象
// URL操作location.href = \'https://new.com\'; // 跳转location.reload(true); // 强制刷新// 浏览器检测if(navigator.userAgent.includes(\'Mobile\')) { console.log(\'移动端访问\');}// 历史记录history.back(); // 后退history.go(-1); // 后退一页
十一、JS 插件使用
Swiper 插件使用流程:
- 引入 CSS/JS 文件
- 复制 HTML 结构
- 初始化配置
<div class=\"swiper\"> <div class=\"swiper-wrapper\"> <div class=\"swiper-slide\">Slide 1</div> <div class=\"swiper-slide\">Slide 2</div> </div></div><script> new Swiper(\'.swiper\', { loop: true, pagination: { el: \'.swiper-pagination\' } });</script>
十二、其他核心内容补充
1. 回调函数
// 异步回调示例function fetchData(callback) { setTimeout(() => { callback(\'数据加载完成\'); }, 1000);}fetchData((result) => { console.log(result);});
2. 表单操作
// 密码可见性切换eyeIcon.addEventListener(\'click\', () => { passwordInput.type = passwordInput.type === \'password\' ? \'text\' : \'password\';});
3. 环境对象 (this)
button.addEventListener(\'click\', function() { console.log(this); // 指向button元素});const obj = { value: 10, getValue() { return this.value; // 指向obj }};
十三、总的来一个E.g
Web API综合实战:响应式任务管理系统
下面通过一个完整案例展示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/font-awesome@4.7.0/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 = ` <input type=\"checkbox\" class=\"task-checkbox mr-3 h-5 w-5 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary/50 transition-colors\" ${task.completed ? \'checked\' : \'\'} > <span class=\"task-text ${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>
核心功能解析
1. DOM 操作
- 动态创建元素:使用
document.createElement()
创建任务项 - 节点添加:通过
appendChild()
将新任务添加到列表 - 属性操作:使用
dataset
存储任务ID,classList
动态修改样式 - 内容更新:通过
innerHTML
和textContent
更新文本和HTML结构
2. 事件处理
- 事件委托:在父容器监听所有任务项的点击和更改事件
- 键盘事件:支持
Escape
清除输入框,Ctrl+D
切换主题 - 事件流控制:使用
preventDefault()
阻止表单默认提交 - 多监听器管理:为不同元素绑定不同类型的事件处理函数
3. 本地存储
- 使用
localStorage
持久化任务数据 - 实现主题偏好保存(明暗模式)
- 页面加载时自动恢复数据状态
4. 定时器与动画
- 任务项添加时的淡入动画
- 滚动时导航栏样式变化的过渡效果
- 使用
requestAnimationFrame
优化性能(隐式应用于CSS过渡)
5. 性能优化
- 使用
DocumentFragment
批量操作DOM(示例中未显式使用,但通过innerHTML
清空再重建实现类似效果) - 条件渲染空状态提示
- 按需显示任务统计信息(根据屏幕尺寸)
6. 响应式设计
- 使用
@media
查询和Tailwind响应式类 - 适配不同屏幕尺寸的布局和元素显示
- 移动端优化:简化UI元素和交互方式
7. 浏览器API
matchMedia
检测用户系统的主题偏好Date
对象处理任务创建时间的格式化Element.classList
实现主题切换的样式管理
拓展建议
- 添加任务编辑功能(双击任务文本)
- 实现任务拖拽排序(使用
drag & drop API
) - 添加任务分类标签系统
- 实现任务截止日期提醒(使用
Notification API
) - 增加深色模式自动切换(基于时间)
这个案例展示了如何综合运用Web API的各种功能构建一个完整的前端应用,涵盖了DOM操作、事件处理、浏览器存储、动画效果等核心知识点。