一、函数基础:参数默认值处理
1. 空值合并运算符(??)实现参数默认值
- 核心作用:仅当参数为
null或undefined时启用默认值(区别于||,不会覆盖0、''等 falsy 值) - 示例代码:
function test(a, b) { // 若 a 是 null/undefined,a 取默认值 10 a = a ?? 10; // 建议补充 b 的默认值逻辑,避免返回 NaN b = b ?? 0; return a + b; } console.log(test()); // 输出 10(a=10,b=0) console.log(test(5)); // 输出 5(a=5,b=0) console.log(test(null, 8)); // 输出 18(a=10,b=8)
二、BOM(浏览器对象模型)核心内容
BOM 是操作浏览器窗口的 API,顶层对象为 window(可省略不写),核心子对象包括 navigator、location、history 等。
1. window 对象(BOM 顶层核心)
(1)全局变量与 window 属性的关联
全局变量 / 函数会自动成为 window 的属性 / 方法,两种访问方式等价:
var globalNum = 20; // 全局变量
console.log(globalNum); // 20(直接访问)
console.log(window.globalNum); // 20(通过 window 访问)
function globalFn() {
console.log('全局函数');
}
globalFn(); // 全局函数
window.globalFn(); // 全局函数
(2)窗口对话框(均阻塞代码,需用户操作后继续)
| 对话框类型 | 语法 | 功能与返回值 |
|---|---|---|
| 提示框 | alert(message) | 仅展示信息,无返回值(点击 “确认” 后执行后续代码) |
| 确认框 | confirm(tips) | 展示 “确定 / 取消”,返回 true(确定)或 false(取消) |
| 输入框 | prompt(tips, [def]) | 支持用户输入,返回输入内容(确定)或 null(取消),def 为可选默认输入值 |
- 示例代码:
// 1. 提示框 alert('请完成表单填写'); // 2. 确认框(删除逻辑示例) if (confirm('确定要删除这条数据吗?')) { console.log('数据删除成功'); } else { console.log('删除操作已取消'); } // 3. 输入框(获取用户名示例) let userName = prompt('请输入您的用户名', '默认用户'); console.log(userName); // 输入内容或 null
(3)定时器任务(延迟 / 轮询)
| 任务类型 | 创建方法 | 取消方法 | 核心说明 |
|---|---|---|---|
| 延迟任务 | setTimeout(fn, delay) | clearTimeout(taskId) | 延迟 delay 毫秒(1 秒 = 1000ms)执行 fn,仅执行 1 次,返回任务 ID |
| 轮询任务 | setInterval(fn, duration) | clearInterval(taskId) | 每隔 duration 毫秒重复执行 fn,返回任务 ID,需手动取消否则持续执行 |
-
示例 1:3 秒后输出内容(延迟任务)
const delayTask = setTimeout(() => { console.log('3秒后执行'); }, 3000); // (可选)3秒内取消任务:clearTimeout(delayTask); -
示例 2:10 秒倒计时(轮询任务)
// 需页面提前添加 <h1>10</h1> let count = 10; const timer = setInterval(() => { const h1 = document.querySelector('h1'); count--; h1.innerText = count; // 倒计时结束,停止轮询 if (count <= 0) { clearInterval(timer); h1.innerText = '时间到!'; } }, 1000);
(4)窗口操作(打开 / 关闭 / 父子关联)
-
window.open(url, target, features):打开新窗口,返回新窗口对象url:新窗口地址(如https://www.baidu.com)target:打开方式(_blank新窗口,_self当前窗口)features:窗口属性(如width=600,height=400,top=100,left=100)
-
window.close():关闭当前窗口(或通过新窗口对象关闭指定窗口) -
window.opener:获取当前窗口的 “打开者”(父窗口) -
示例代码:
// 打开新窗口,3秒后自动关闭 const newWin = window.open( 'https://www.baidu.com', '_blank', 'width=600,height=400,top=100,left=100' ); setTimeout(() => { newWin.close(); // 关闭新窗口 }, 3000);
(5)其他常用方法 / 属性
| 方法 / 属性 | 功能 |
|---|---|
window.btoa(str) | Base64 编码(仅支持 Latin-1 字符,中文需先 encodeURI) |
window.atob(encodedStr) | Base64 解码(解码后需 decodeURI 还原中文) |
window.scrollX/scrollY | 获取水平 / 垂直滚动条滚动距离(单位:像素) |
window.scrollTo(x, y) | 滚动到指定位置(如 scrollTo({top: 0, left: 0}) 回到页面顶部) |
window.scrollBy(x, y) | 相对当前位置偏移滚动(如 scrollBy({top: 200}) 向下滚 200 像素) |
- 中文 Base64 编解码示例:
// 中文编码:先转义再编码 const encoded = btoa(encodeURI('前端笔记')); console.log(encoded); // 输出 "5Yqh5ouG5pWw5o2u" // 中文解码:先解码再还原 const decoded = decodeURI(atob(encoded)); console.log(decoded); // 输出 "前端笔记"
2. navigator 对象(浏览器信息)
用于获取浏览器及设备相关信息,核心属性:
navigator.userAgent:获取浏览器 “用户代理” 字符串(用于判断浏览器类型 / 版本)console.log(navigator.userAgent); // 示例输出:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36navigator.cookieEnabled:判断浏览器是否启用 Cookie(返回true/false)console.log(navigator.cookieEnabled); // true(若启用 Cookie)navigator.geolocation.getCurrentPosition():获取设备地理位置(需用户授权)// 成功回调:获取经纬度并解析地址(示例用高德地图API) navigator.geolocation.getCurrentPosition( (position) => { const lat = position.coords.latitude; // 纬度 const lng = position.coords.longitude; // 经度 const key = '你的高德地图APIKey'; // 需替换为自己的APIKey // 调用高德地图逆地理编码API fetch(`https://restapi.amap.com/v3/geocode/regeo?key=${key}&location=${lng},${lat}`) .then(res => res.json()) .then(data => { if (data.status === '1') { document.querySelector('#addr').innerText = `当前地址:${data.regeocode.formatted_address}`; } }); }, (error) => { console.log('定位失败:', error.message); } ); // 页面需添加 <div id="addr"></div> 用于展示地址
3. location 对象(当前网址操作)
(1)获取网址各组成部分
| 属性名 | 功能说明 |
|---|---|
location.protocol | 获取协议(如 http:、https:) |
location.hostname | 获取主机 / 域名(如 www.baidu.com、127.0.0.1) |
location.port | 获取端口号(http 默认 80、https 默认 443,无显式端口则返回空字符串) |
location.pathname | 获取请求路径(如 /index.html、/user/profile) |
location.search | 获取查询参数(如 ?name=张三&age=20) |
location.hash | 获取锚点(如 #section1,用于页面内定位) |
location.href | 获取完整网址(如 https://www.baidu.com/s?wd=js),也可用于跳转 |
- 示例代码:
console.log('协议:', location.protocol); console.log('域名:', location.hostname); console.log('查询参数:', location.search); console.log('完整网址:', location.href);
(2)页面跳转方法
| 方法名 | 功能说明 |
|---|---|
location.href = url | 跳转到指定 url,会生成历史记录(可通过 history.back() 后退) |
location.assign(url) | 与 href 效果一致,跳转到指定 url 并生成历史记录 |
location.replace(url) | 跳转到指定 url,不生成历史记录(无法通过 history.back() 后退) |
- 示例代码:
// 1秒后跳转到百度(生成历史记录) setTimeout(() => { location.href = 'https://www.baidu.com'; // 或 location.assign('https://www.baidu.com'); }, 1000); // 跳转到谷歌(不生成历史记录) // location.replace('https://www.google.com');
4. history 对象(历史记录操作)
用于操作浏览器的访问历史(前进 / 后退),核心方法:
| 方法名 | 功能说明 |
|---|---|
history.back() | 后退到上一页(等价于浏览器 “后退” 按钮) |
history.forward() | 前进到下一页(等价于浏览器 “前进” 按钮) |
history.go(n) | 跳转到历史记录中第 n 页(n=1 前进 1 页,n=-1 后退 1 页,n=0 刷新当前页) |
- 示例代码:
// 后退1页 document.querySelector('#backBtn').addEventListener('click', () => { history.back(); }); // 前进1页 document.querySelector('#forwardBtn').addEventListener('click', () => { history.forward(); }); // 刷新当前页 history.go(0);
三、DOM(文档对象模型)核心操作
DOM 是操作 HTML 文档结构的 API,核心流程:选中元素 → 操作元素。
1. 元素选择器(选中元素的核心方法)
(1)常用选择器对比
| 选择器方法 | 语法示例 | 返回值类型 | 核心特点 |
|---|---|---|---|
querySelector(cssSelector) | document.querySelector('#box') | 单个元素对象(Element) | 支持所有 CSS 选择器,返回第一个匹配元素,无匹配则返回 null |
querySelectorAll(cssSelector) | document.querySelectorAll('.item') | NodeList(类数组) | 支持所有 CSS 选择器,返回所有匹配元素,无匹配则返回空 NodeList |
getElementById(id) | document.getElementById('username') | 单个元素对象 | 按 id 选择,id 需页面唯一,性能优,无匹配则返回 null |
getElementsByTagName(tag) | document.getElementsByTagName('p') | HTMLCollection(类数组) | 按标签名选择,实时更新(DOM 变化时自动同步) |
getElementsByClassName(className) | document.getElementsByClassName('active') | HTMLCollection | 按 class 选择,实时更新 |
getElementsByName(name) | document.getElementsByName('hobby') | NodeList | 按 name 属性选择(常用于表单元素,如单选框、复选框) |
- 示例代码:
// 1. querySelector(选第一个 .link 类元素) const firstLink = document.querySelector('.link'); // 2. querySelectorAll(选所有 p 标签下的 a 标签) const allLinks = document.querySelectorAll('p a'); allLinks.forEach(link => { console.log(link.innerText); }); // 3. getElementById(选 id 为 header 的元素) const header = document.getElementById('header'); // 4. getElementsByTagName(选所有 div 标签) const allDivs = document.getElementsByTagName('div');
(2)基础 CSS 选择器参考(配合 querySelector 使用)
| 选择器类型 | 语法示例 | 功能说明 |
|---|---|---|
| 通用选择器 | * | 选所有元素 |
| 标签选择器 | p、a | 选指定标签元素 |
| 类选择器 | .active | 选 class="active" 的元素 |
| ID 选择器 | #box | 选 id="box" 的元素 |
| 属性选择器 | [href]、[type="text"] | 选有指定属性或属性值匹配的元素 |
| 后代选择器 | div p | 选 div 内部所有 p 元素(包括子、孙等后代) |
| 子选择器 | div > p | 选 div 直接子元素中的 p(仅一级子元素) |
| 相邻兄弟选择器 | p + a | 选 p 元素后紧邻的第一个 a 元素 |
| 通用兄弟选择器 | p ~ a | 选 p 元素后所有同级的 a 元素 |
| 分组选择器 | p, a, div | 同时选 p、a、div 元素 |
| 伪类选择器 | a:hover、li:first-child | 按状态(hover)或位置(first-child)选择 |
| 伪元素选择器 | p::before、div::after | 选择元素的伪元素(需配合样式操作,JS 无法直接选中伪元素节点) |
2. 元素标签操作(选中元素后的核心操作)
(1)获取标签基础信息
| 属性名 | 功能说明 |
|---|---|
tagName | 获取标签名(全大写,如 P、A),Element 节点特有 |
nodeName | 获取节点名(标签节点返回全大写标签名,文本节点返回 #text) |
- 示例代码:
const pTag = document.querySelector('p'); console.log('标签名(tagName):', pTag.tagName); // P console.log('节点名(nodeName):', pTag.nodeName); // P
(2)操作标签体内容(文本 / 超文本)
| 属性名 | 功能说明 | 特点 |
|---|---|---|
innerText | 获取 / 设置标签体纯文本内容 | 自动过滤 HTML 标签,设置时会将 < 转义为 <(仅显示文本) |
textContent | 与 innerText 类似,获取 / 设置纯文本内容 | 更规范(W3C 标准),会保留空格和换行,兼容性稍好 |
innerHTML | 获取 / 设置标签体超文本内容 | 保留 HTML 标签,设置时可解析标签(如设置 <a href="#">链接</a> 会生成链接) |
outerHTML | 获取标签自身 + 内部的超文本内容(设置时会替换整个标签) | 包含标签本身,如 <p><a>hello</a></p>,慎用设置功能 |
- 示例代码(页面需提前添加
<p><a href="">hello</a></p>):const pTag = document.querySelector('p'); // 1. 获取内容 console.log('innerText(纯文本):', pTag.innerText); // hello console.log('innerHTML(超文本):', pTag.innerHTML); // <a href="">hello</a> console.log('outerHTML(自身+内部):', pTag.outerHTML); // <p><a href="">hello</a></p> // 2. 设置内容 pTag.innerText = '<a href="#">world</a>'; // 页面显示:<a href="#">world</a>(纯文本) pTag.innerHTML = '<a href="#">world</a>'; // 页面显示:world(可点击的链接)
(3)操作标签特有属性(如表单、链接、图片)
标签特有的属性(如 a 标签 href、img 标签 src、表单 value)支持直接通过 . 访问和修改:
一、DOM 交互实战案例
1. 复选框全选 / 反选与选中内容展示
功能需求
- 实现 “全选” 复选框控制所有子复选框(爱好)的选中状态
- 子复选框选中状态变化时,同步 “全选” 框状态
- 实时展示当前选中的爱好名称
核心代码与解析
<!-- HTML 结构 -->
<input type="checkbox" name="" id="all" onchange="checkHobby(this)">全选
<hr>
<input type="checkbox" name="" id="" onchange="ckHobby()">游泳
<input type="checkbox" name="" id="" onchange="ckHobby()">爬山
<input type="checkbox" name="" id="" onchange="ckHobby()">看书
<input type="checkbox" name="" id="" onchange="ckHobby()">听音乐
<p>当前选中的爱好有 : [<span></span>]</p>
<script>
// 1. 提前获取所有子复选框(排除全选框)和展示选中内容的 span
let checkList = document.querySelectorAll("input[type=checkbox]:not(#all)");
let spanIs = document.querySelector('p span');
// 2. 全选框改变事件:控制所有子复选框
function checkHobby(tag) {
const isChecked = tag.checked; // 获取全选框当前状态
// 遍历子复选框,同步状态
for (let item of checkList) {
item.checked = isChecked;
}
// 调用更新选中内容的逻辑(复用代码)
updateSelectedHobbies();
}
// 3. 子复选框改变事件:同步全选框状态
function ckHobby() {
let isAllChecked = true; // 假设初始为全选状态
// 遍历子复选框,判断是否有未选中的
for (let item of checkList) {
if (!item.checked) {
isAllChecked = false;
break; // 有一个未选中则跳出循环
}
}
// 同步全选框状态
document.querySelector('#all').checked = isAllChecked;
// 更新选中内容
updateSelectedHobbies();
}
// 4. 复用函数:更新当前选中的爱好文本
function updateSelectedHobbies() {
const selected = [];
for (let item of checkList) {
if (item.checked) {
// 获取复选框后的文本(需去除空格)
selected.push(item.nextSibling.textContent.trim());
}
}
// 拼接选中内容并展示
spanIs.innerText = selected.join('、');
}
</script>
关键知识点
querySelectorAll("input[type=checkbox]:not(#all)"):使用 CSS 伪类:not()排除指定元素nextSibling.textContent:获取元素的下一个兄弟节点(文本节点)的内容- 逻辑复用:将 “更新选中内容” 抽取为独立函数,避免代码冗余
2. 星级评分(点击星星高亮)
功能需求
- 点击某个星星时,该星星及左侧所有星星高亮(红色),右侧星星恢复默认色(米色)
核心代码与解析
<style>
span {
color: beige; /* 默认色:米色 */
cursor: pointer; /* 鼠标悬浮为手型,提示可点击 */
font-size: 24px; /* 放大星星,增强交互感 */
}
</style>
<p><span>❤</span><span>❤</span><span>❤</span><span>❤</span><span>❤</span></p>
<script>
// 1. 获取所有星星元素
let spanList = document.querySelectorAll('span');
// 2. 为每个星星绑定点击事件
for (let star of spanList) {
star.addEventListener('click', function() {
// 步骤1:先将所有星星恢复默认色
spanList.forEach(s => s.style.color = 'beige');
// 步骤2:高亮当前星星及左侧所有星星
for (let s of spanList) {
s.style.color = 'red'; // 高亮为红色
if (s === this) break; // 遇到当前点击的星星则停止
}
});
}
</script>
关键知识点
addEventListener:事件绑定(相比onclick更灵活,支持多事件绑定)- 遍历逻辑:先 “重置” 所有元素状态,再 “高亮” 目标及左侧元素,逻辑清晰
3. 用户名 / 密码框切换
功能需求
- 点击按钮实现 “用户名输入框” 与 “密码输入框” 的切换(含文本提示和输入类型切换)
- 切换时清空输入框内容,避免敏感信息残留
核心代码与解析
<!-- HTML 结构 -->
<p>用户名:<input class="input-box" type="text"></p>
<input type="button" value="切换" class="switch-btn">
<script>
// 1. 获取按钮、文本提示父容器、输入框
let switchBtn = document.querySelector('.switch-btn');
let textContainer = document.querySelector('p');
let inputBox = document.querySelector('.input-box');
// 2. 状态标记:true 表示当前是用户名框,false 表示密码框
let isUsername = true;
// 3. 按钮点击事件
switchBtn.addEventListener('click', function() {
inputBox.value = ''; // 切换时清空输入内容
if (isUsername) {
// 切换为密码框:修改文本提示和输入类型
textContainer.firstChild.textContent = "密码:";
inputBox.type = 'password';
} else {
// 切换为用户名框:恢复文本提示和输入类型
textContainer.firstChild.textContent = "用户名:";
inputBox.type = 'text';
}
// 切换状态标记
isUsername = !isUsername;
});
</script>
关键知识点
input.type:动态修改输入框类型(text或password)firstChild.textContent:修改父容器中第一个文本节点的内容(实现 “用户名 / 密码” 提示切换)
4. 2 小时倒计时(含开始 / 暂停 / 继续 / 结束功能)
功能需求
- 初始显示
02:00:00,点击 “开始” 启动倒计时 - 支持 “暂停”(停止倒计时但保留剩余时间)、“继续”(恢复倒计时)、“结束”(重置为初始状态)
核心代码与解析
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2小时倒计时</title>
<script>
// 1. 初始化变量:总秒数(2小时 = 2*60*60 = 7200秒)、定时器ID、倒计时状态
let totalSeconds = 2 * 60 * 60;
let timerId = null; // 存储定时器ID,用于暂停/结束
let isRunning = false; // 标记是否正在倒计时
// 2. 格式化时间:将秒数转为 "HH:MM:SS" 格式(补前导0)
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600).toString().padStart(2, '0');
const minutes = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
const secs = (seconds % 60).toString().padStart(2, '0');
return `${hours}:${minutes}:${secs}`;
}
// 3. 开始倒计时
function startCountdown() {
if (isRunning) return; // 避免重复启动
isRunning = true;
timerId = setInterval(() => {
totalSeconds--;
// 倒计时结束:清除定时器,重置状态
if (totalSeconds <= 0) {
clearInterval(timerId);
isRunning = false;
totalSeconds = 2 * 60 * 60; // 重置为初始值
}
// 更新页面显示
document.querySelector('.time').innerText = formatTime(totalSeconds);
}, 1000); // 每秒执行一次
}
// 4. 暂停倒计时
function pauseCountdown() {
if (!isRunning) return;
clearInterval(timerId);
isRunning = false;
}
// 5. 结束倒计时(重置为初始状态)
function endCountdown() {
clearInterval(timerId);
isRunning = false;
totalSeconds = 2 * 60 * 60;
document.querySelector('.time').innerText = formatTime(totalSeconds);
}
</script>
</head>
<body>
<h1 class="time">02:00:00</h1>
<button onclick="startCountdown()">开始</button>
<button onclick="pauseCountdown()">暂停</button>
<button onclick="startCountdown()">继续</button> <!-- 继续复用开始逻辑 -->
<button onclick="endCountdown()">结束</button>
</body>
</html>
关键知识点
padStart(2, '0'):字符串补全方法,确保时间格式为两位数(如1→01)- 定时器控制:通过
timerId管理定时器,实现 “暂停”(clearInterval)和 “继续”(重新启动定时器) - 状态标记
isRunning:避免重复启动定时器导致倒计时加速
5. 表单验证(用户名 / 密码 / 邮箱)
功能需求
- 用户名:仅允许字母、数字、下划线
- 密码:长度 6~20 位
- 确认密码:与密码一致
- 邮箱:符合标准邮箱格式(含
@和域名后缀) - 点击 “提交” 时触发所有校验,有错误则提示并终止提交
核心代码与解析
<!-- HTML 结构 -->
<p>用户名: <input class="username" type="text" placeholder="仅字母、数字、下划线"></p>
<p>密码: <input class="password" type="password" placeholder="6-20位"></p>
<p>确认密码: <input class="repassword" type="password"></p>
<p>邮箱: <input class="email" type="text" placeholder="如 xxx@xxx.com"></p>
<p><input class="submit-btn" type="button" value="提交"></p>
<script>
const submitBtn = document.querySelector('.submit-btn');
// 提交按钮点击事件
submitBtn.addEventListener('click', function() {
// 1. 获取所有输入值(trim() 去除首尾空格)
const username = document.querySelector('.username').value.trim();
const password = document.querySelector('.password').value;
const repassword = document.querySelector('.repassword').value;
const email = document.querySelector('.email').value.trim();
// 2. 用户名校验:正则 /^[0-9a-zA-Z_]+$/ 匹配字母、数字、下划线
if (!username) {
alert('用户名不能为空!');
return; // 有错误则终止后续校验
} else if (!/^[0-9a-zA-Z_]+$/.test(username)) {
alert('用户名仅允许字母、数字、下划线!');
return;
}
// 3. 密码长度校验
if (password.length < 6 || password.length > 20) {
alert('密码长度必须为 6~20 位!');
return;
}
// 4. 确认密码校验
if (password !== repassword) {
alert('两次输入的密码不一致!');
return;
}
// 5. 邮箱格式校验:标准邮箱正则(含 @、域名后缀)
const emailReg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailReg.test(email)) {
alert('请输入正确的邮箱格式(如 xxx@xxx.com)!');
return;
}
// 6. 所有校验通过
alert('表单验证通过,提交成功!');
});
</script>
关键知识点
- 正则表达式应用:
- 用户名:
/^[0-9a-zA-Z_]+$/(^开头,$结尾,确保整个字符串符合规则) - 邮箱:
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/(匹配用户名、@、域名、后缀)
- 用户名:
- 校验逻辑顺序:先校验非空,再校验格式 / 长度,最后校验一致性,逻辑清晰
6. 猜灯谜小游戏(调用第三方 API)
功能需求
- 页面加载时自动获取灯谜(调用第三方 API)
- 用户有 3 次答题机会,答错提示剩余次数,3 次答错显示正确答案和解析
- 答对可切换下一题,支持回车键提交答案
核心代码与解析
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>猜灯谜</title>
<script>
// 全局变量:当前灯谜数据、错误次数、最大错误次数、API密钥
let currentRiddle = null;
let errorCount = 0;
const MAX_ERRORS = 3;
const API_KEY = 'd9ea9ddca4f048b487a70dd8e29f3547'; // 第三方API密钥(需自行申请)
// 页面加载完成后自动获取灯谜
window.onload = fetchRiddle;
// 1. 获取灯谜(调用第三方API)
function fetchRiddle() {
// 重置状态:错误次数、提示文本、输入框状态
errorCount = 0;
document.getElementById('errorMessage').textContent = '';
document.getElementById('answerInput').value = '';
document.getElementById('correctAnswer').style.display = 'none';
document.getElementById('answerInput').disabled = false;
document.getElementById('submitBtn').disabled = false;
document.getElementById('nextRiddleBtn').style.display = 'none';
// 调用API获取随机灯谜
fetch(`https://api.tianapi.com/lantern/random?key=${API_KEY}`)
.then(response => response.json())
.then(data => {
currentRiddle = data.newslist[0]; // 提取灯谜数据
document.getElementById('riddleQuestion').textContent = currentRiddle.question;
})
.catch(error => {
alert('获取灯谜失败,请刷新页面重试!');
console.error('API请求错误:', error);
});
}
// 2. 验证答案
function checkAnswer() {
if (!currentRiddle) return; // 无灯谜数据时不执行
const userAnswer = document.getElementById('answerInput').value.trim();
const correctAnswer = currentRiddle.answer.trim();
// 答案不区分大小写
if (userAnswer.toLowerCase() === correctAnswer.toLowerCase()) {
document.getElementById('errorMessage').textContent = '恭喜你,答对了!';
document.getElementById('errorMessage').style.color = 'green';
document.getElementById('nextRiddleBtn').style.display = 'inline-block';
} else {
errorCount++;
// 提示剩余次数
document.getElementById('errorMessage').textContent = `答错了!还有${MAX_ERRORS - errorCount}次机会`;
document.getElementById('errorMessage').style.color = 'red';
// 3次答错:显示正确答案和解析,禁用输入框
if (errorCount >= MAX_ERRORS) {
document.getElementById('correctAnswer').innerHTML = `
<p>正确答案:${currentRiddle.answer}</p>
<p>解析:${currentRiddle.explanation || '暂无解析'}</p>
`;
document.getElementById('correctAnswer').style.display = 'block';
document.getElementById('nextRiddleBtn').style.display = 'inline-block';
document.getElementById('answerInput').disabled = true;
document.getElementById('submitBtn').disabled = true;
}
}
}
// 3. 下一题(复用获取灯谜逻辑)
function nextRiddle() {
fetchRiddle();
}
// 4. 回车键提交答案
function handleKeyPress(event) {
if (event.key === 'Enter') {
checkAnswer();
}
}
</script>
</head>
<body>
<h1>猜灯谜</h1>
<div>
<h3 id="riddleQuestion">加载中...</h3>
<p>请输入答案:</p>
<input type="text" id="answerInput" onkeypress="handleKeyPress(event)">
<button id="submitBtn" onclick="checkAnswer()">提交答案</button>
<button id="nextRiddleBtn" onclick="nextRiddle()" style="display: none;">下一个灯谜</button>
<p id="errorMessage"></p>
<div id="correctAnswer" style="display: none; margin-top: 10px; padding: 10px; border: 1px solid #ccc;"></div>
</div>
<p>提示:您有3次答题机会,3次错误后将显示正确答案和解析</p>
</body>
</html>
关键知识点
fetchAPI:用于调用第三方 API 获取数据,需处理异步逻辑(then/catch)- 状态管理:通过全局变量
currentRiddle、errorCount管理游戏状态 - 键盘事件:
onkeypress监听回车键,提升用户体验
二、正则表达式实战
1. 提取字符串中的所有数字并求和
需求:从混合字符串中提取所有连续数字,转为数字后求和
const str = '46546asdafa87gfg654dg';
// 1. 正则 /\d+/g:匹配1个及以上连续数字,g表示全局匹配
const numArr = str.match(/\d+/g);
// 2. 求和(先判断是否有匹配结果,避免报错)
const sum = numArr ? numArr.reduce((total, numStr) => total + parseInt(numStr), 0) : 0;
console.log(sum); // 46546 + 87 + 654 = 47287
2. 将字符串中的所有数字替换为 **
需求:将字符串中每个数字字符替换为 **
const str = '1sdad45s64ad';
// 正则 /\d/g:匹配单个数字,g表示全局替换
const newStr = str.replaceAll(/\d/g, '**');
console.log(newStr); // **sdad****s****ad
3. 手机号中间 4 位脱敏
需求:将手机号 15455367889 处理为 154****7889
const phone = '15455367889';
// 正则分组:(\d{3}) 匹配前3位,(\d{4}) 匹配中间4位,(\d{4}) 匹配后4位
const desensitizedPhone = phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3');
console.log(desensitizedPhone); // 154****7889
4. 邮箱用户名脱敏
需求:将邮箱 16544987465654@qq.com 处理为 1***@qq.com
const email = '16544987465654@qq.com';
// 正则分组:(.) 匹配第一个字符,.* 匹配中间任意字符,(@.*$) 匹配@及后面所有内容
const desensitizedEmail = email.replace(/(.).*(@.*$)/, '$1***$2');
console.log(desensitizedEmail); // 1***@qq.com
5. 提取文件路径中的文件名和扩展名
需求:从路径 /home/user/docs/report.pdf 中提取文件名 report 和扩展名 .pdf
const path = '/home/user/docs/report.pdf';
// 正则解析:
// ([^/]*?):匹配最后一个/后的非/字符(非贪婪,避免包含.)
// (\.[^.]*)$:匹配.及后面非.字符(直到字符串结束,即扩展名)
const matchResult = path.match(/([^/]*?)(\.[^.]*)$/);
if (matchResult) {
const fileName = matchResult[1]; // report
const extName = matchResult[2]; // .pdf
console.log(`文件名:${fileName},扩展名:${extName}`);
}
6. 查找字符串中最长的字母单词
需求:从混合字符串中提取所有字母组成的单词,找到最长的一个
const str = '11465sdada55dasdwasd789522adsadada';
// 正则 /[a-z]+/ig:匹配所有字母(不区分大小写),g全局匹配
const words = str.match(/[a-z]+/ig);
// 找到最长单词(reduce 遍历比较长度)
const longestWord = words.reduce((max, word) => word.length > max.length ? word : max, '');
console.log(longestWord); // adsadada(长度8)
7. 驼峰命名转脊柱命名
需求:将 fontSizeKiss 转为 font-size-kiss
const camelCase = 'fontSizeKiss';
// 正则 /[A-Z]/g:匹配大写字母,替换为 -+小写字母
const kebabCase = camelCase.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
console.log(kebabCase); // font-size-kiss
三、数组与对象实战
1. 多数组交叉组合
需求:将 [1, 2] 和 [3, 4] 组合为 [[1,3], [1,4], [2,3], [2,4]]
function combineArrays(...arrays) {
// 边界处理:无输入数组返回空数组
if (arrays.length === 0) return [];
// 初始值:将第一个数组的每个元素转为数组(如 [1,2] → [[1], [2]])
let result = arrays[0].map(item => [item]);
// 遍历后续数组,与现有结果交叉组合
for (let i = 1; i < arrays.length; i++) {
const temp = [];
// 现有结果中的每个组合,与当前数组的每个元素拼接
result.forEach(combo => {
arrays[i].forEach(item => {
temp.push([...combo, item]); // 展开现有组合,添加新元素
});
});
result = temp; // 更新结果
}
return result;
}
// 测试
console.log(combineArrays([1,2], [3,4])); // [[1,3],[1,4],[2,3],[2,4]]
console.log(combineArrays([1], [2,3], [4])); // [[1,2,4], [1,3,4]]
2. 员工数据多维度处理
需求:基于员工数组,实现筛选、排序、求和、分组等操作
const employees = [
{ name: 'Alice', age: 30, department: 'Engineering' },
{ name: 'Bob', age: 25, department: 'Marketing' },
{ name: 'Charlie', age: 35, department: 'Engineering' },
{ name: 'David', age: 40, department: 'Sales' }
];
// 1. 获取 Engineering 部门员工
const engineeringEmp = employees.filter(emp => emp.department === 'Engineering');
// 2. 按年龄升序排序(注意:sort 会修改原数组,需先复制)
const sortedByAge = [...employees].sort((a, b) => a.age - b.age);
// 3. 计算所有员工年龄和
const totalAge = employees.reduce((sum, emp) => sum + emp.age, 0);
// 4. 统计各部门人数
const deptCount = employees.reduce((acc, emp) => {
acc[emp.department] = (acc[emp.department] || 0) + 1;
return acc;
}, {});
// 5. 按部门分组(值为员工数组)
const deptGroup = employees.reduce((acc, emp) => {
if (!acc[emp.department]) acc[emp.department] = [];
acc[emp.department].push(emp);
return acc;
}, {});
// 6. 查找年龄最大的员工
const oldestEmp = employees.reduce((oldest, current) =>
current.age > oldest.age ? current : oldest, employees[0]
);
// 输出结果
console.log('1. Engineering部门员工:', engineeringEmp);
console.log('2. 按年龄升序:', sortedByAge);
console.log('3. 总年龄:', totalAge);
console.log('4. 各部门人数:', deptCount);
console.log('5. 按部门分组:', deptGroup);
console.log('6. 年龄最大员工:', oldestEmp);
3. 实现可遍历的自定义对象(for...of 支持)
需求:自定义对象支持 push/unshift/pop/shift 等数组方法,并可通过 for...of 遍历
let obj = {
values: [], // 存储数据的内部数组
// 1. 向尾部添加元素
push(...val) {
this.values.push(...val);
return this.values.length;
},
// 2. 向头部添加元素
unshift(...val) {
this.values.unshift(...val);
return this.values.length;
},
// 3. 移除尾部元素
pop() {
return this.values.pop();
},
// 4. 移除头部元素
shift() {
return this.values.shift();
},
// 5. 获取指定索引元素
get(index) {
return index >= 0 && index < this.values.length ? this.values[index] : undefined;
},
// 6. 修改指定索引元素
set(index, value) {
if (index >= 0 && index < this.values.length) {
this.values[index] = value;
}
},
// 7. 转为字符串
toString() {
return `[${this.values.join(', ')}]`;
},
// 8. 实现迭代器(支持 for...of 遍历)
[Symbol.iterator]() {
let index = 0;
const values = this.values;
return {
next() {
return index < values.length
? { value: values[index++], done: false }
: { done: true };
}
};
}
};
// 测试
obj.push(1, 2, 3);
for (let val of obj) {
console.log(val); // 1, 2, 3
}
四、解构赋值实战
1. 数组混合解构(提取部门员工)
需求:从部门数组中提取 HR 部门第一个员工、IT 部门最后一个员工
const departments = [
{ name: 'HR', employees: ['Alice', 'Bob', 'Charlie'] },
{ name: 'IT', employees: ['Dave', 'Eve', 'Frank'] }
];
// 解构:HR 部门取第一个员工,IT 部门取第三个(最后一个)员工
const [{ employees: [firstHREmployee] }, { employees: [, , lastITEmployee] }] = departments;
console.log(firstHREmployee); // Alice
console.log(lastITEmployee); // Frank
2. 对象复杂解构(提取配置项)
需求:从配置对象中提取服务器地址(重命名)、超时时间(默认值)、重试次数(默认值)
javascript
运行
const config = {
server: { host: 'localhost', port: 8080 },
timeout: 0,
retries: undefined
};
// 解构:重命名 host/port,timeout 为0时用默认值5000,retries 未定义时用默认值3
const {
server: { host: serverHost, port: serverPort },
timeout: timeout = 5000,
retries: retries = 3
} = config;
console.log(serverHost); // localhost
console.log(serverPort); // 8080
console.log(timeout); // 0(原值为0,不触发默认值)
console.log(retries); // 3(原值为undefined,触发默认值)
3. 解构函数返回值
需求:从函数返回的嵌套对象中提取 id、name、country
function getUserProfile() {
return {
id: 123,
info: {
name: 'Sarah',
location: { city: 'New York', country: 'USA' }
}
};
}
// 嵌套解构:提取 id、info.name、info.location.country
const {
id: userId,
info: {
name: userName,
location: { country: userCountry }
}
} = getUserProfile();
console.log(userId); // 123
console.log(userName); // Sarah
console.log(userCountry); // USA
五、工具函数实战
1. 随机生成 RGB 颜色
需求:编写函数 randomColor,返回 rgb(r, g, b) 格式的随机颜色(r/g/b 范围 0~255)
function randomColor() {
// Math.random() 生成 0~1 随机数,*256 后取整(0~255)
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r}, ${g}, ${b})`;
}
// 测试:生成随机颜色并设置页面背景
document.body.style.backgroundColor = randomColor();
2. 查找所有三位数水仙花数
需求:水仙花数是指一个 3 位数,其各位数字的立方和等于该数本身(如 153 = 1³ + 5³ + 3³)
const narcissisticNumbers = [];
// 遍历所有三位数(100~999)
for (let i = 100; i <= 999; i++) {
const hundreds = Math.floor(i / 100); // 百位
const tens = Math.floor((i % 100) / 10); // 十位
const units = i % 10; // 个位
// 判断是否为水仙花数
if (Math.pow(hundreds, 3) + Math.pow(tens, 3) + Math.pow(units, 3) === i) {
narcissisticNumbers.push(i);
}
}
console.log('三位数水仙花数:', narcissisticNumbers); // [153, 370, 371, 407]
3. 数字转中文大写(12 位以内)
需求:将数字 1234 转为 壹仟贰佰叁拾肆,处理中间含 0 的情况(如 1001 → 壹仟零壹)
function numberToChinese(numStr) {
// 边界校验:1~12位数字
if (!/^\d{1,12}$/.test(numStr)) return "请输入1-12位数字";
// 中文数字和单位(对应 个、十、百、千、万、十万、百万、千万、亿、十亿、百亿、千亿)
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟'];
let result = '';
// 遍历数字的每一位,拼接“数字+单位”
for (let i = 0; i < numStr.length; i++) {
const digit = parseInt(numStr[i]);
const unitIndex = numStr.length - 1 - i; // 计算当前位的单位索引
result += digits[digit] + units[unitIndex];
}
// 处理冗余的“零”(关键步骤)
result = result
.replace(/零[拾佰仟]/g, '零') // 去掉“零十”“零百”等
.replace(/零+/g, '零') // 多个零合并为一个
.replace(/零(万|亿)/g, '$1') // 去掉“零万”“零亿”中的零
.replace(/零$/, ''); // 去掉末尾的零
// 特殊情况:结果为空时返回“零”
return result || '零';
}
// 测试
console.log(numberToChinese('1234')); // 壹仟贰佰叁拾肆
console.log(numberToChinese('1001')); // 壹仟零壹
console.log(numberToChinese('105')); // 壹佰零伍
console.log(numberToChinese('0')); // 零
4. BOM 实战:自动填充当前网址信息
需求:页面加载时,将当前网址的协议、域名、端口、请求地址、锚点自动填充到对应输入框
<!-- HTML 结构 -->
协议 : <input class="protocol" type="text"><br>
域名 : <input class="hostname" type="text"><br>
端口号: <input class="port" type="text"><br>
请求地址 : <input class="pathname" type="text"><br>
锚点 : <input class="hash" type="text"><br>
<script>
// 页面加载完成后填充
window.onload = function() {
// 从 location 对象中获取各部分信息
document.querySelector('.protocol').value = location.protocol.slice(0, -1); // 去掉末尾的冒号(如 http: → http)
document.querySelector('.hostname').value = location.hostname;
document.querySelector('.port').value = location.port || '80(http默认)/443(https默认)'; // 无端口时提示默认值
document.querySelector('.pathname').value = location.pathname;
document.querySelector('.hash').value = location.hash.slice(1); // 去掉开头的#(如 #section1 → section1)
};
</script>
5. 扩展 RegExp 原型:实现指定次数替换
需求:给 RegExp 类添加 sub 方法,支持替换指定次数的匹配内容(默认替换所有)
RegExp.prototype.sub = function(string, newStr, count = -1) {
const regex = this;
let match;
let result = string;
let replaceCount = 0;
// 情况1:count=-1(默认),替换所有
if (count === -1) {
// 若原正则无 g 标志,添加 g 后替换
const globalRegex = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
return result.replace(globalRegex, newStr);
}
// 情况2:count>0,替换指定次数
// 若原正则有 g 标志,先去掉(避免全局匹配导致计数问题)
const nonGlobalRegex = new RegExp(regex.source, regex.flags.replace('g', ''));
while ((match = nonGlobalRegex.exec(result)) !== null && replaceCount < count) {
// 替换当前匹配项
result = result.slice(0, match.index) + newStr + result.slice(match.index + match[0].length);
// 更新下一次匹配的起始位置(避免重复匹配同一位置)
nonGlobalRegex.lastIndex = match.index + newStr.length;
replaceCount++;
}
return result;
};
// 测试
const str = 'xyz12345adsda1113';
const regex = /\d+/;
console.log(regex.sub(str, '*****', 2)); // xyz*****adsda*****3(替换前2个数字组合)
console.log(regex.sub(str, '*****')); // xyz*****adsda*****(替换所有数字组合)

968

被折叠的 条评论
为什么被折叠?



