feat: add TypeScript lessons and learning panel

- Introduced a new script to check TypeScript lesson files for errors.
- Created a main TypeScript file to render lessons and their details.
- Added lesson definitions with starter and answer codes.
- Implemented a user interface for navigating and running lessons.
- Styled the application with CSS for a better user experience.
- Updated README to reflect the new TypeScript section and usage instructions.
This commit is contained in:
charlie
2026-03-19 10:06:11 +08:00
parent 69a4ae3178
commit f3bdaa4e88
146 changed files with 5951 additions and 9 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,28 @@
# 练习 1获取元素
## 目标
学会用几种常见方式拿到页面元素。
## 你要练什么
- `getElementById`
- `querySelector`
- `querySelectorAll`
- 元素文本读取
## 任务
请基于页面结构完成以下操作:
- 选中主标题
- 选中“开始学习”按钮
- 选中全部学习卡片
- 在控制台输出标题文字、按钮文字和卡片数量
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/01-query-selectors/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/01-query-selectors/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/01-query-selectors/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/01-query-selectors/answer.js)

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>获取元素</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe3f0;
}
.cards {
display: grid;
gap: 12px;
margin-top: 18px;
}
.card {
padding: 16px;
border-radius: 14px;
background: #f8fbff;
border: 1px solid #dce8f8;
}
</style>
</head>
<body>
<section class="panel">
<h1 id="page-title">DOM 获取元素练习</h1>
<button class="start-btn" type="button">开始学习</button>
<div class="cards">
<article class="card">获取标题</article>
<article class="card">获取按钮</article>
<article class="card">获取一组卡片</article>
</div>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,7 @@
const title = document.getElementById("page-title");
const button = document.querySelector(".start-btn");
const cards = document.querySelectorAll(".card");
console.log("标题:", title.textContent);
console.log("按钮:", button.textContent);
console.log("卡片数量:", cards.length);

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>获取元素</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe3f0;
}
.cards {
display: grid;
gap: 12px;
margin-top: 18px;
}
.card {
padding: 16px;
border-radius: 14px;
background: #f8fbff;
border: 1px solid #dce8f8;
}
</style>
</head>
<body>
<section class="panel">
<h1 id="page-title">DOM 获取元素练习</h1>
<button class="start-btn" type="button">开始学习</button>
<div class="cards">
<article class="card">获取标题</article>
<article class="card">获取按钮</article>
<article class="card">获取一组卡片</article>
</div>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,5 @@
// 任务:
// 1. 用 getElementById 获取标题
// 2. 用 querySelector 获取按钮
// 3. 用 querySelectorAll 获取全部卡片
// 4. 在控制台输出标题文字、按钮文字和卡片数量

View File

@@ -0,0 +1,25 @@
# 练习 2修改文本、类名和样式
## 目标
学会改元素内容、切换类名和设置简单样式。
## 你要练什么
- `textContent`
- `classList.add`
- `classList.remove`
- `style`
## 任务
- 把标题改成“今天已完成 DOM 练习”
- 给状态标签加上完成样式
- 修改说明文字颜色
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/02-text-class-style/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/02-text-class-style/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/02-text-class-style/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/02-text-class-style/answer.js)

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>修改文本、类名和样式</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f3f6fb;
}
.card {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #d9e3f2;
}
.badge {
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: #eef2f7;
color: #4b5563;
}
.badge.done {
background: #dcfce7;
color: #166534;
}
</style>
</head>
<body>
<section class="card">
<h1 id="title">今天的学习状态</h1>
<p id="description">当前还没有更新进度。</p>
<span id="badge" class="badge">进行中</span>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,9 @@
const title = document.getElementById("title");
const description = document.getElementById("description");
const badge = document.getElementById("badge");
title.textContent = "今天已完成 DOM 练习";
description.textContent = "已经练习了元素选择、文本修改和样式切换。";
description.style.color = "#2563eb";
badge.textContent = "已完成";
badge.classList.add("done");

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>修改文本、类名和样式</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f3f6fb;
}
.card {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #d9e3f2;
}
.badge {
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: #eef2f7;
color: #4b5563;
}
.badge.done {
background: #dcfce7;
color: #166534;
}
</style>
</head>
<body>
<section class="card">
<h1 id="title">今天的学习状态</h1>
<p id="description">当前还没有更新进度。</p>
<span id="badge" class="badge">进行中</span>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,5 @@
// 任务:
// 1. 选中标题、描述、状态标签
// 2. 修改标题文字
// 3. 给标签加上 done 类名
// 4. 修改描述文字颜色

View File

@@ -0,0 +1,24 @@
# 练习 3创建和删除节点
## 目标
学会动态新增和删除页面节点。
## 你要练什么
- `createElement`
- `appendChild`
- `remove`
- 事件绑定
## 任务
- 点击“新增任务”时往列表里加一个新项
- 点击“删除最后一项”时删除最后一个列表项
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/03-create-and-remove/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/03-create-and-remove/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/03-create-and-remove/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/03-create-and-remove/answer.js)

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>创建和删除节点</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
ul {
padding-left: 20px;
}
button {
margin-right: 10px;
}
</style>
</head>
<body>
<section class="panel">
<h1>任务列表</h1>
<button id="add-btn" type="button">新增任务</button>
<button id="remove-btn" type="button">删除最后一项</button>
<ul id="task-list">
<li>学习 querySelector</li>
<li>学习 classList</li>
</ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
const addButton = document.getElementById("add-btn");
const removeButton = document.getElementById("remove-btn");
const taskList = document.getElementById("task-list");
let taskIndex = 3;
addButton.addEventListener("click", function () {
const item = document.createElement("li");
item.textContent = `新任务 ${taskIndex}`;
taskList.appendChild(item);
taskIndex += 1;
});
removeButton.addEventListener("click", function () {
const lastItem = taskList.lastElementChild;
if (lastItem) {
lastItem.remove();
}
});

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>创建和删除节点</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
ul {
padding-left: 20px;
}
button {
margin-right: 10px;
}
</style>
</head>
<body>
<section class="panel">
<h1>任务列表</h1>
<button id="add-btn" type="button">新增任务</button>
<button id="remove-btn" type="button">删除最后一项</button>
<ul id="task-list">
<li>学习 querySelector</li>
<li>学习 classList</li>
</ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
// 任务:
// 1. 获取新增按钮、删除按钮和列表
// 2. 点击新增按钮时创建一个新的 li 并追加到列表
// 3. 点击删除按钮时删除最后一个 li

View File

@@ -0,0 +1,25 @@
# 练习 4点击计数器
## 目标
学会给按钮绑定点击事件,并更新页面数据。
## 你要练什么
- `addEventListener`
- 点击事件
- 数字状态更新
- DOM 渲染
## 任务
- 点击加一按钮时计数加 1
- 点击减一按钮时计数减 1
- 点击重置按钮时恢复为 0
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/04-click-counter/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/04-click-counter/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/04-click-counter/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/04-click-counter/answer.js)

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>点击计数器</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f4f7fb;
}
.counter {
width: 320px;
padding: 24px;
text-align: center;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe3ef;
}
#value {
font-size: 48px;
font-weight: 800;
}
.actions {
display: flex;
justify-content: center;
gap: 10px;
}
</style>
</head>
<body>
<section class="counter">
<h1>点击计数器</h1>
<p id="value">0</p>
<div class="actions">
<button id="decrease-btn" type="button">-1</button>
<button id="increase-btn" type="button">+1</button>
<button id="reset-btn" type="button">重置</button>
</div>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,25 @@
let count = 0;
const value = document.getElementById("value");
const decreaseButton = document.getElementById("decrease-btn");
const increaseButton = document.getElementById("increase-btn");
const resetButton = document.getElementById("reset-btn");
function render() {
value.textContent = count;
}
decreaseButton.addEventListener("click", function () {
count -= 1;
render();
});
increaseButton.addEventListener("click", function () {
count += 1;
render();
});
resetButton.addEventListener("click", function () {
count = 0;
render();
});

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>点击计数器</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f4f7fb;
}
.counter {
width: 320px;
padding: 24px;
text-align: center;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe3ef;
}
#value {
font-size: 48px;
font-weight: 800;
}
.actions {
display: flex;
justify-content: center;
gap: 10px;
}
</style>
</head>
<body>
<section class="counter">
<h1>点击计数器</h1>
<p id="value">0</p>
<div class="actions">
<button id="decrease-btn" type="button">-1</button>
<button id="increase-btn" type="button">+1</button>
<button id="reset-btn" type="button">重置</button>
</div>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
let count = 0;
// 任务:
// 1. 获取数字元素和 3 个按钮
// 2. 点击按钮时更新 count
// 3. 每次修改后,把最新 count 渲染到页面

View File

@@ -0,0 +1,26 @@
# 练习 5表单提交和 preventDefault
## 目标
学会拦截表单默认提交,并把输入内容渲染到页面。
## 你要练什么
- `submit` 事件
- `preventDefault()`
- 表单值读取
- 动态追加列表项
## 任务
- 提交表单时阻止页面刷新
- 读取输入框内容
- 把内容加入到待办列表
- 提交后清空输入框
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/05-form-submit-and-prevent-default/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/05-form-submit-and-prevent-default/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/05-form-submit-and-prevent-default/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/05-form-submit-and-prevent-default/answer.js)

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>表单提交和 preventDefault</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
form {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px 12px;
}
</style>
</head>
<body>
<section class="panel">
<h1>学习待办</h1>
<form id="todo-form">
<input id="todo-input" type="text" placeholder="输入今天要学的内容" />
<button type="submit">添加</button>
</form>
<ul id="todo-list">
<li>完成 DOM 选择练习</li>
</ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,18 @@
const form = document.getElementById("todo-form");
const input = document.getElementById("todo-input");
const list = document.getElementById("todo-list");
form.addEventListener("submit", function (event) {
event.preventDefault();
const value = input.value.trim();
if (!value) {
return;
}
const item = document.createElement("li");
item.textContent = value;
list.appendChild(item);
input.value = "";
});

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>表单提交和 preventDefault</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
form {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px 12px;
}
</style>
</head>
<body>
<section class="panel">
<h1>学习待办</h1>
<form id="todo-form">
<input id="todo-input" type="text" placeholder="输入今天要学的内容" />
<button type="submit">添加</button>
</form>
<ul id="todo-list">
<li>完成 DOM 选择练习</li>
</ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
// 任务:
// 1. 获取表单、输入框和列表
// 2. 监听 submit 事件
// 3. 用 preventDefault() 阻止默认提交
// 4. 读取输入框内容,创建新 li追加到列表
// 5. 清空输入框

View File

@@ -0,0 +1,25 @@
# 练习 6冒泡、委托和 stopPropagation
## 目标
理解事件会冒泡,并学会在列表里使用事件委托。
## 你要练什么
- 事件冒泡
- `event.target`
- 事件委托
- `stopPropagation()`
## 任务
- 点击外层面板时输出一条日志
- 点击列表项时,通过事件委托切换激活状态
- 点击列表项里的删除按钮时,阻止冒泡并删除当前项
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/06-bubbling-and-delegation/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/06-bubbling-and-delegation/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/06-bubbling-and-delegation/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/06-bubbling-and-delegation/answer.js)

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>冒泡、委托和 stopPropagation</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe2ee;
}
.lesson-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
border: 1px solid #dbe2ee;
border-radius: 12px;
margin-top: 10px;
}
.lesson-item.active {
background: #dbeafe;
}
</style>
</head>
<body>
<section id="panel" class="panel">
<h1>事件委托练习</h1>
<p>点击列表项可以切换高亮,点击删除按钮可以移除当前项。</p>
<ul id="lesson-list">
<li class="lesson-item">
<span>事件冒泡</span>
<button class="remove-btn" type="button">删除</button>
</li>
<li class="lesson-item">
<span>事件委托</span>
<button class="remove-btn" type="button">删除</button>
</li>
</ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
const panel = document.getElementById("panel");
const lessonList = document.getElementById("lesson-list");
panel.addEventListener("click", function () {
console.log("点击到了外层面板");
});
lessonList.addEventListener("click", function (event) {
const removeButton = event.target.closest(".remove-btn");
if (removeButton) {
event.stopPropagation();
const currentItem = removeButton.closest(".lesson-item");
if (currentItem) {
currentItem.remove();
}
return;
}
const currentItem = event.target.closest(".lesson-item");
if (currentItem) {
currentItem.classList.toggle("active");
}
});

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>冒泡、委托和 stopPropagation</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe2ee;
}
.lesson-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
border: 1px solid #dbe2ee;
border-radius: 12px;
margin-top: 10px;
}
.lesson-item.active {
background: #dbeafe;
}
</style>
</head>
<body>
<section id="panel" class="panel">
<h1>事件委托练习</h1>
<p>点击列表项可以切换高亮,点击删除按钮可以移除当前项。</p>
<ul id="lesson-list">
<li class="lesson-item">
<span>事件冒泡</span>
<button class="remove-btn" type="button">删除</button>
</li>
<li class="lesson-item">
<span>事件委托</span>
<button class="remove-btn" type="button">删除</button>
</li>
</ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,5 @@
// 任务:
// 1. 给 panel 绑定点击事件,输出一条日志
// 2. 给 lesson-list 绑定点击事件,使用事件委托
// 3. 点击 li 时切换 active 类名
// 4. 点击删除按钮时,阻止冒泡并删除当前 li

View File

@@ -0,0 +1,25 @@
# 练习 7setTimeout 和异步顺序
## 目标
理解同步代码和异步回调的执行先后顺序。
## 你要练什么
- `setTimeout`
- 同步顺序
- 异步回调
- DOM 日志输出
## 任务
- 点击按钮后先输出“开始执行”
- 再立刻输出“同步代码结束”
- 然后延迟输出“异步回调完成”
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/07-timers-and-async-order/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/07-timers-and-async-order/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/07-timers-and-async-order/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/07-timers-and-async-order/answer.js)

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>setTimeout 和异步顺序</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
#log-list li {
margin-top: 8px;
}
</style>
</head>
<body>
<section class="panel">
<h1>异步顺序练习</h1>
<button id="run-btn" type="button">开始执行</button>
<ul id="log-list"></ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
const runButton = document.getElementById("run-btn");
const logList = document.getElementById("log-list");
function appendLog(text) {
const item = document.createElement("li");
item.textContent = text;
logList.appendChild(item);
}
runButton.addEventListener("click", function () {
logList.innerHTML = "";
appendLog("开始执行");
setTimeout(function () {
appendLog("异步回调完成");
}, 600);
appendLog("同步代码结束");
});

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>setTimeout 和异步顺序</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 720px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
#log-list li {
margin-top: 8px;
}
</style>
</head>
<body>
<section class="panel">
<h1>异步顺序练习</h1>
<button id="run-btn" type="button">开始执行</button>
<ul id="log-list"></ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
// 任务:
// 1. 获取按钮和日志列表
// 2. 点击按钮后清空旧日志
// 3. 先追加“开始执行”
// 4. 用 setTimeout 延迟追加“异步回调完成”
// 5. 再立刻追加“同步代码结束”

View File

@@ -0,0 +1,26 @@
# 练习 8Promise 和渲染
## 目标
学会在 Promise 完成后把结果渲染到页面。
## 你要练什么
- Promise
- `.then()`
- `catch()`
- loading 状态
## 任务
- 点击按钮后显示“加载中”
- 等待 Promise 返回数据
- 把课程名称渲染到列表
- 如果失败,显示错误信息
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/08-promise-and-render/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/08-promise-and-render/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/08-promise-and-render/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/08-promise-and-render/answer.js)

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promise 和渲染</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f4f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce3ef;
}
</style>
</head>
<body>
<section class="panel">
<h1>Promise 数据渲染</h1>
<button id="load-btn" type="button">加载课程</button>
<p id="status-text">等待加载</p>
<ul id="course-list"></ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
function fakeFetchCourses() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(["DOM 获取元素", "事件监听", "异步基础"]);
}, 800);
});
}
const loadButton = document.getElementById("load-btn");
const statusText = document.getElementById("status-text");
const courseList = document.getElementById("course-list");
loadButton.addEventListener("click", function () {
statusText.textContent = "加载中...";
courseList.innerHTML = "";
fakeFetchCourses()
.then(function (courses) {
courses.forEach(function (course) {
const item = document.createElement("li");
item.textContent = course;
courseList.appendChild(item);
});
statusText.textContent = "加载完成";
})
.catch(function () {
statusText.textContent = "加载失败";
});
});

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promise 和渲染</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f4f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce3ef;
}
</style>
</head>
<body>
<section class="panel">
<h1>Promise 数据渲染</h1>
<button id="load-btn" type="button">加载课程</button>
<p id="status-text">等待加载</p>
<ul id="course-list"></ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,14 @@
function fakeFetchCourses() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(["DOM 获取元素", "事件监听", "异步基础"]);
}, 800);
});
}
// 任务:
// 1. 获取按钮、状态文字、列表
// 2. 点击按钮后显示“加载中”
// 3. 调用 fakeFetchCourses()
// 4. 用 then 渲染课程列表
// 5. 用 catch 处理错误

View File

@@ -0,0 +1,26 @@
# 练习 9async / await 面板
## 目标
学会用 `async` / `await` 写一个更直观的异步流程。
## 你要练什么
- `async`
- `await`
- `try...catch`
- DOM 更新
## 任务
- 点击按钮后进入加载状态
- 等待异步函数返回用户信息
- 把结果渲染到卡片里
- 如果失败,显示错误信息
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/09-async-await-panel/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/09-async-await-panel/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/09-async-await-panel/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/09-async-await-panel/answer.js)

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>async / await 面板</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dbe4ef;
}
</style>
</head>
<body>
<section class="panel">
<h1>用户信息面板</h1>
<button id="load-user-btn" type="button">加载用户信息</button>
<p id="user-status">等待加载</p>
<div id="user-card"></div>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
function fakeFetchUser() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve({
name: "林晨",
role: "前端学习者",
focus: "DOM + 事件 + 异步",
});
}, 900);
});
}
const loadButton = document.getElementById("load-user-btn");
const statusText = document.getElementById("user-status");
const userCard = document.getElementById("user-card");
async function loadUser() {
statusText.textContent = "加载中...";
userCard.innerHTML = "";
try {
const user = await fakeFetchUser();
userCard.innerHTML = `
<p>姓名:${user.name}</p>
<p>身份:${user.role}</p>
<p>当前重点:${user.focus}</p>
`;
statusText.textContent = "加载完成";
} catch (error) {
statusText.textContent = "加载失败";
}
}
loadButton.addEventListener("click", loadUser);

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>async / await 面板</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dbe4ef;
}
</style>
</head>
<body>
<section class="panel">
<h1>用户信息面板</h1>
<button id="load-user-btn" type="button">加载用户信息</button>
<p id="user-status">等待加载</p>
<div id="user-card"></div>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,18 @@
function fakeFetchUser() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve({
name: "林晨",
role: "前端学习者",
focus: "DOM + 事件 + 异步",
});
}, 900);
});
}
// 任务:
// 1. 获取按钮、状态文字、信息面板
// 2. 写一个 async 函数
// 3. 用 await 等待 fakeFetchUser()
// 4. 渲染用户信息
// 5. 用 try...catch 处理异常

View File

@@ -0,0 +1,34 @@
# 练习 10综合页面
## 目标
把 DOM、事件和异步知识拼起来做一个可以真实互动的小页面。
## 项目名称
学习面板
## 任务
请完成一个控制台之外可见的页面交互,要求至少包含:
- 一个课程列表区域
- 一个表单区域
- 一个添加课程的交互
- 一个点击课程切换完成状态的交互
- 一个异步加载提示或远程数据模拟
- 清晰的状态文案更新
## 建议顺序
1. 先看 HTML 结构
2. 先写元素获取和渲染函数
3. 再写表单提交和点击事件
4. 最后补异步加载逻辑
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/10-final-dashboard/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/10-final-dashboard/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/10-final-dashboard/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/10-final-dashboard/answer.js)

View File

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>学习面板</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: linear-gradient(180deg, #eef4ff 0%, #f7faff 100%);
color: #0f172a;
}
.page {
width: min(900px, calc(100% - 24px));
margin: 0 auto;
}
.hero,
.panel,
.form-panel {
margin-top: 18px;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe4f0;
}
.lesson-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 12px 14px;
border-radius: 12px;
background: #f8fbff;
border: 1px solid #dbe4f0;
}
.lesson-item.done {
background: #dcfce7;
border-color: #bbf7d0;
}
form {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px 12px;
}
</style>
</head>
<body>
<main class="page">
<section class="hero">
<h1>DOM + 事件 + 异步学习面板</h1>
<p id="status-text">正在初始化页面...</p>
</section>
<section class="panel">
<h2>课程列表</h2>
<button id="load-btn" type="button">模拟加载课程</button>
<ul id="lesson-list"></ul>
</section>
<section class="form-panel">
<h2>添加课程</h2>
<form id="lesson-form">
<input id="lesson-input" type="text" placeholder="输入课程名称" />
<button type="submit">添加</button>
</form>
</section>
</main>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,84 @@
const lessons = [
{ title: "获取元素", done: false },
{ title: "修改 DOM", done: true },
];
function fakeLoadExtraLessons() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve([
{ title: "事件委托", done: false },
{ title: "异步渲染", done: false },
]);
}, 900);
});
}
const statusText = document.getElementById("status-text");
const loadButton = document.getElementById("load-btn");
const lessonList = document.getElementById("lesson-list");
const lessonForm = document.getElementById("lesson-form");
const lessonInput = document.getElementById("lesson-input");
function renderLessons() {
lessonList.innerHTML = "";
lessons.forEach(function (lesson, index) {
const item = document.createElement("li");
item.className = "lesson-item";
if (lesson.done) {
item.classList.add("done");
}
item.dataset.index = index;
item.textContent = lesson.title;
lessonList.appendChild(item);
});
}
lessonList.addEventListener("click", function (event) {
const currentItem = event.target.closest(".lesson-item");
if (!currentItem) {
return;
}
const index = Number(currentItem.dataset.index);
lessons[index].done = !lessons[index].done;
renderLessons();
});
lessonForm.addEventListener("submit", function (event) {
event.preventDefault();
const value = lessonInput.value.trim();
if (!value) {
return;
}
lessons.push({
title: value,
done: false,
});
lessonInput.value = "";
statusText.textContent = "已新增一门课程";
renderLessons();
});
loadButton.addEventListener("click", async function () {
statusText.textContent = "正在异步加载课程...";
const newLessons = await fakeLoadExtraLessons();
newLessons.forEach(function (lesson) {
lessons.push(lesson);
});
statusText.textContent = "额外课程加载完成";
renderLessons();
});
statusText.textContent = "页面初始化完成";
renderLessons();

View File

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>学习面板</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: linear-gradient(180deg, #eef4ff 0%, #f7faff 100%);
color: #0f172a;
}
.page {
width: min(900px, calc(100% - 24px));
margin: 0 auto;
}
.hero,
.panel,
.form-panel {
margin-top: 18px;
padding: 24px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe4f0;
}
.lesson-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 12px 14px;
border-radius: 12px;
background: #f8fbff;
border: 1px solid #dbe4f0;
}
.lesson-item.done {
background: #dcfce7;
border-color: #bbf7d0;
}
form {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px 12px;
}
</style>
</head>
<body>
<main class="page">
<section class="hero">
<h1>DOM + 事件 + 异步学习面板</h1>
<p id="status-text">正在初始化页面...</p>
</section>
<section class="panel">
<h2>课程列表</h2>
<button id="load-btn" type="button">模拟加载课程</button>
<ul id="lesson-list"></ul>
</section>
<section class="form-panel">
<h2>添加课程</h2>
<form id="lesson-form">
<input id="lesson-input" type="text" placeholder="输入课程名称" />
<button type="submit">添加</button>
</form>
</section>
</main>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
const lessons = [
{ title: "获取元素", done: false },
{ title: "修改 DOM", done: true },
];
function fakeLoadExtraLessons() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve([
{ title: "事件委托", done: false },
{ title: "异步渲染", done: false },
]);
}, 900);
});
}
// 任务:
// 1. 获取页面里的关键元素
// 2. 写一个 renderLessons 函数,把 lessons 渲染到列表
// 3. 点击列表项时切换 done 状态
// 4. 提交表单时阻止默认提交,并新增课程
// 5. 点击加载按钮时显示加载状态,并把异步返回的课程追加到列表

View File

@@ -0,0 +1,25 @@
# 练习 11input 实时预览
## 目标
学会处理输入类事件,让页面随着用户输入实时变化。
## 你要练什么
- `input` 事件
- `change` 事件
- 表单值读取
- 实时 DOM 更新
## 任务
- 在输入框输入昵称时,实时更新预览标题
- 在文本域输入学习目标时,实时更新预览内容
- 切换学习阶段下拉框时,更新阶段标签
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/11-input-live-preview/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/11-input-live-preview/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/11-input-live-preview/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/11-input-live-preview/answer.js)

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>input 实时预览</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.page {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
max-width: 960px;
margin: 0 auto;
}
.panel {
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
label {
display: grid;
gap: 8px;
margin-top: 12px;
}
input,
textarea,
select {
padding: 10px 12px;
font: inherit;
}
.badge {
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: #dbeafe;
color: #1d4ed8;
}
</style>
</head>
<body>
<main class="page">
<section class="panel">
<h1>编辑资料</h1>
<label for="nickname-input">
昵称
<input id="nickname-input" type="text" placeholder="输入你的昵称" />
</label>
<label for="goal-input">
学习目标
<textarea id="goal-input" rows="4" placeholder="输入今天的学习目标"></textarea>
</label>
<label for="stage-select">
学习阶段
<select id="stage-select">
<option value="入门阶段">入门阶段</option>
<option value="DOM 阶段">DOM 阶段</option>
<option value="异步阶段">异步阶段</option>
</select>
</label>
</section>
<section class="panel">
<p class="badge" id="preview-stage">入门阶段</p>
<h2 id="preview-name">未填写昵称</h2>
<p id="preview-goal">这里会显示你的学习目标。</p>
</section>
</main>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
const nicknameInput = document.getElementById("nickname-input");
const goalInput = document.getElementById("goal-input");
const stageSelect = document.getElementById("stage-select");
const previewStage = document.getElementById("preview-stage");
const previewName = document.getElementById("preview-name");
const previewGoal = document.getElementById("preview-goal");
nicknameInput.addEventListener("input", function () {
const value = nicknameInput.value.trim();
previewName.textContent = value || "未填写昵称";
});
goalInput.addEventListener("input", function () {
const value = goalInput.value.trim();
previewGoal.textContent = value || "这里会显示你的学习目标。";
});
stageSelect.addEventListener("change", function () {
previewStage.textContent = stageSelect.value;
});

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>input 实时预览</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f6f8fb;
}
.page {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
max-width: 960px;
margin: 0 auto;
}
.panel {
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
label {
display: grid;
gap: 8px;
margin-top: 12px;
}
input,
textarea,
select {
padding: 10px 12px;
font: inherit;
}
.badge {
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
background: #dbeafe;
color: #1d4ed8;
}
</style>
</head>
<body>
<main class="page">
<section class="panel">
<h1>编辑资料</h1>
<label for="nickname-input">
昵称
<input id="nickname-input" type="text" placeholder="输入你的昵称" />
</label>
<label for="goal-input">
学习目标
<textarea id="goal-input" rows="4" placeholder="输入今天的学习目标"></textarea>
</label>
<label for="stage-select">
学习阶段
<select id="stage-select">
<option value="入门阶段">入门阶段</option>
<option value="DOM 阶段">DOM 阶段</option>
<option value="异步阶段">异步阶段</option>
</select>
</label>
</section>
<section class="panel">
<p class="badge" id="preview-stage">入门阶段</p>
<h2 id="preview-name">未填写昵称</h2>
<p id="preview-goal">这里会显示你的学习目标。</p>
</section>
</main>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
// 任务:
// 1. 获取昵称输入框、目标文本域、阶段下拉框
// 2. 监听昵称和目标的 input 事件
// 3. 把输入内容实时渲染到右侧预览
// 4. 监听阶段下拉框的 change 事件
// 5. 更新阶段标签文字

View File

@@ -0,0 +1,25 @@
# 练习 12prepend、classList.remove 和链接默认行为
## 目标
补齐几个常见但容易漏掉的 DOM 细节操作。
## 你要练什么
- `prepend()`
- `classList.remove()`
- 链接点击事件
- `preventDefault()`
## 任务
- 点击“插入到最前面”时,把一条新消息插入列表顶部
- 点击“清除高亮”时,移除全部高亮类名
- 点击帮助链接时,阻止默认跳转,并在页面显示提示信息
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/12-prepend-remove-and-link-default/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/12-prepend-remove-and-link-default/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/12-prepend-remove-and-link-default/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/12-prepend-remove-and-link-default/answer.js)

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>prepend、classList.remove 和链接默认行为</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
.item {
margin-top: 10px;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid #dbe4f0;
}
.item.active {
background: #dbeafe;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
</style>
</head>
<body>
<section class="panel">
<h1>补漏练习</h1>
<div class="actions">
<button id="prepend-btn" type="button">插入到最前面</button>
<button id="clear-active-btn" type="button">清除高亮</button>
<a id="help-link" href="https://example.com/help">查看帮助</a>
</div>
<p id="hint-text">这里会显示操作提示。</p>
<ul id="message-list">
<li class="item active">先学事件监听</li>
<li class="item active">再学异步顺序</li>
<li class="item">最后做综合页面</li>
</ul>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,31 @@
const prependButton = document.getElementById("prepend-btn");
const clearActiveButton = document.getElementById("clear-active-btn");
const helpLink = document.getElementById("help-link");
const hintText = document.getElementById("hint-text");
const messageList = document.getElementById("message-list");
let messageIndex = 1;
prependButton.addEventListener("click", function () {
const item = document.createElement("li");
item.className = "item active";
item.textContent = `新插入的提醒 ${messageIndex}`;
messageList.prepend(item);
hintText.textContent = "已把一条消息插入到最前面";
messageIndex += 1;
});
clearActiveButton.addEventListener("click", function () {
const items = document.querySelectorAll(".item");
items.forEach(function (item) {
item.classList.remove("active");
});
hintText.textContent = "已移除全部高亮状态";
});
helpLink.addEventListener("click", function (event) {
event.preventDefault();
hintText.textContent = "已阻止默认跳转";
});

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>prepend、classList.remove 和链接默认行为</title>
<style>
body {
margin: 0;
padding: 32px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fb;
}
.panel {
max-width: 760px;
margin: 0 auto;
padding: 24px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #dce4ef;
}
.item {
margin-top: 10px;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid #dbe4f0;
}
.item.active {
background: #dbeafe;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
</style>
</head>
<body>
<section class="panel">
<h1>补漏练习</h1>
<div class="actions">
<button id="prepend-btn" type="button">插入到最前面</button>
<button id="clear-active-btn" type="button">清除高亮</button>
<a id="help-link" href="https://example.com/help">查看帮助</a>
</div>
<p id="hint-text">这里会显示操作提示。</p>
<ul id="message-list">
<li class="item active">先学事件监听</li>
<li class="item active">再学异步顺序</li>
<li class="item">最后做综合页面</li>
</ul>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
// 任务:
// 1. 获取两个按钮、帮助链接、提示文字和列表
// 2. 点击 prepend-btn 时创建新 li并插入到列表最前面
// 3. 点击 clear-active-btn 时移除所有 item 的 active 类名
// 4. 点击帮助链接时,用 preventDefault() 阻止跳转
// 5. 在提示文字里输出“已阻止默认跳转”

View File

@@ -0,0 +1,176 @@
# DOM + 事件 + 异步
这部分只解决一个问题:你能不能让页面里的元素被 JavaScript 真正控制起来,并对用户操作和异步结果做出响应。
## 学完后你应该掌握
- 如何获取页面元素
- 如何修改文本、样式和类名
- 如何创建、插入和删除节点
- 如何监听点击、输入、提交等事件
- `preventDefault()``stopPropagation()` 的常见场景
- 事件委托的基本写法
- `prepend()``classList.remove()` 的基础使用
- `setTimeout` 的异步顺序
- Promise 的基础用法
- `async` / `await` 的基础写法
- 如何把 DOM、事件、异步组合成一个小页面
## 这一章在解决什么
前面三章分别解决了:
- 页面有什么结构
- 页面长什么样
- 代码逻辑怎么写
这一章开始JavaScript 不再只是打印到控制台,而是要真正操作页面。
它回答的是:
- 我怎么拿到某个按钮或输入框
- 用户点击后页面怎么变
- 表单提交后怎么处理
- 数据晚一点回来时页面怎么更新
## 必须建立的 5 个核心意识
### 1. 先选中元素,再操作元素
DOM 操作第一步不是“改”,而是“找到”。
常见方式:
- `getElementById`
- `querySelector`
- `querySelectorAll`
### 2. 事件是页面和用户的连接点
页面本身不会“自动响应”。
是你通过事件监听告诉浏览器:
- 点了按钮要做什么
- 输入框变化后要做什么
- 提交表单要做什么
### 3. 改页面就是改 DOM
常见 DOM 改动包括:
- 改文字:`textContent`
- 改类名:`classList`
- 改样式:`style`
- 增删节点:`appendChild``remove`
### 4. 异步不是“慢一点执行”,而是“先不阻塞主流程”
比如:
- `setTimeout`
- Promise
- `async` / `await`
这些都意味着:当前代码先继续往下走,结果稍后回来再处理。
### 5. 一个完整交互通常是“事件 + DOM + 状态 + 异步”
例如点击“加载数据”按钮时:
1. 监听点击事件
2. 更新 loading 状态
3. 等待异步结果
4. 把结果渲染到页面
## 高频 API 速记
### 获取元素
- `document.getElementById()`
- `document.querySelector()`
- `document.querySelectorAll()`
### 修改元素
- `textContent`
- `innerHTML`
- `style`
- `classList.add()`
- `classList.remove()`
- `classList.toggle()`
### 节点操作
- `document.createElement()`
- `appendChild()`
- `prepend()`
- `remove()`
### 事件
- `addEventListener()`
- `event.target`
- `preventDefault()`
- `stopPropagation()`
### 异步
- `setTimeout()`
- `Promise`
- `.then()`
- `async`
- `await`
## 学习顺序
1. 获取元素
2. 修改文本、类名、样式
3. 创建和删除节点
4. 点击事件
5. 表单提交和 `preventDefault`
6. 冒泡、委托和 `stopPropagation`
7. `setTimeout` 和异步顺序
8. Promise 渲染
9. `async` / `await`
10. 综合小页面
11. `input` / `change` 事件
12. `prepend``classList.remove` 和链接默认行为
## 练习目录
- [01-query-selectors/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/01-query-selectors/README.md)
- [02-text-class-style/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/02-text-class-style/README.md)
- [03-create-and-remove/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/03-create-and-remove/README.md)
- [04-click-counter/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/04-click-counter/README.md)
- [05-form-submit-and-prevent-default/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/05-form-submit-and-prevent-default/README.md)
- [06-bubbling-and-delegation/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/06-bubbling-and-delegation/README.md)
- [07-timers-and-async-order/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/07-timers-and-async-order/README.md)
- [08-promise-and-render/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/08-promise-and-render/README.md)
- [09-async-await-panel/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/09-async-await-panel/README.md)
- [10-final-dashboard/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/10-final-dashboard/README.md)
- [11-input-live-preview/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/11-input-live-preview/README.md)
- [12-prepend-remove-and-link-default/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/04-dom-events-async/12-prepend-remove-and-link-default/README.md)
## 过关标准
如果你能独立做到下面这些,就说明这一章已经基本过关:
- 能正确选中页面里的元素
- 能改文字、类名和内联样式
- 能动态创建和删除列表项
- 能给按钮、表单绑定事件
- 能理解事件冒泡和事件委托
- 能在需要时使用 `preventDefault()``stopPropagation()`
- 能处理 `input` / `change` 这类常见表单事件
- 能使用 `prepend()``classList.remove()` 完成简单 DOM 调整
- 能理解 `setTimeout` 的异步顺序
- 能用 Promise 和 `async` / `await` 处理一个简单异步流程
- 能做出一个有真实交互的小页面
## 学习建议
- 每个练习都用浏览器打开 `starter.html`
- 打开开发者工具,边看页面边看控制台
- 一个交互没反应时,先确认元素有没有选中
- 一个异步结果不对时,先打印中间状态

View File

@@ -0,0 +1,24 @@
# 练习 1模板字符串和解构
## 目标
学会用模板字符串拼接信息,并用解构快速读取对象和数组数据。
## 你要练什么
- 模板字符串
- 对象解构
- 数组解构
## 任务
-`student` 对象里解构出姓名和阶段
-`scores` 数组里解构出前两个分数
- 用模板字符串把这些信息渲染到页面
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/01-template-and-destructuring/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/01-template-and-destructuring/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/01-template-and-destructuring/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/01-template-and-destructuring/answer.js)

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模板字符串和解构</title>
</head>
<body>
<section>
<h1>ES6+ 练习 1</h1>
<p id="output">等待渲染</p>
</section>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
const student = {
name: "林晨",
stage: "ES6+",
};
const scores = [88, 92, 95];
const { name, stage } = student;
const [firstScore, secondScore] = scores;
document.getElementById("output").textContent = `${name} 正在学习 ${stage},前两次练习分数分别是 ${firstScore}${secondScore}`;

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模板字符串和解构</title>
</head>
<body>
<section>
<h1>ES6+ 练习 1</h1>
<p id="output">等待渲染</p>
</section>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
const student = {
name: "林晨",
stage: "ES6+",
};
const scores = [88, 92, 95];
// 任务:
// 1. 解构出 name、stage
// 2. 解构出前两个分数
// 3. 用模板字符串把信息写入 #output

View File

@@ -0,0 +1,24 @@
# 练习 2展开运算符和剩余参数
## 目标
学会复制数组、合并对象,以及用剩余参数接收不定数量的参数。
## 你要练什么
- 数组展开
- 对象展开
- 剩余参数
## 任务
- 复制一份课程数组并新增一项
- 基于用户对象生成一个更新后的对象
- 写一个 `sumScores` 函数,用剩余参数计算总分
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/02-spread-and-rest/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/02-spread-and-rest/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/02-spread-and-rest/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/02-spread-and-rest/answer.js)

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>展开运算符和剩余参数</title>
</head>
<body>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
const tracks = ["HTML", "CSS", "JavaScript"];
const user = {
name: "林晨",
stage: "基础阶段",
};
function sumScores(...scores) {
return scores.reduce((total, score) => total + score, 0);
}
const newTracks = [...tracks, "ES6+"];
const nextUser = { ...user, stage: "现代 JS" };
const totalScore = sumScores(88, 91, 95);
document.getElementById("output").textContent = JSON.stringify(
{
newTracks,
nextUser,
totalScore,
},
null,
2
);

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>展开运算符和剩余参数</title>
</head>
<body>
<pre id="output"></pre>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
const tracks = ["HTML", "CSS", "JavaScript"];
const user = {
name: "林晨",
stage: "基础阶段",
};
function sumScores(...scores) {
// 返回总分
}
// 任务:
// 1. 复制 tracks 并新增 "ES6+"
// 2. 基于 user 生成一个 stage 为 "现代 JS" 的新对象
// 3. 调用 sumScores
// 4. 输出结果到 #output

View File

@@ -0,0 +1,23 @@
# 练习 3箭头函数
## 目标
学会把简单函数改写成箭头函数。
## 你要练什么
- 箭头函数
- 简写返回值
- 数组映射
## 任务
- 把两个普通函数改成箭头函数
-`map` 和箭头函数生成课程标签
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/03-arrow-functions/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/03-arrow-functions/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/03-arrow-functions/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/03-arrow-functions/answer.js)

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>箭头函数</title>
</head>
<body>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,15 @@
const getLevel = (score) => (score >= 80 ? "达标" : "继续练习");
const add = (a, b) => a + b;
const tracks = ["DOM", "异步", "模块化"];
const labels = tracks.map((track) => `[${track}]`);
document.getElementById("output").textContent = JSON.stringify(
{
level: getLevel(86),
sum: add(12, 8),
labels,
},
null,
2
);

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>箭头函数</title>
</head>
<body>
<pre id="output"></pre>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,14 @@
function getLevel(score) {
return score >= 80 ? "达标" : "继续练习";
}
function add(a, b) {
return a + b;
}
const tracks = ["DOM", "异步", "模块化"];
// 任务:
// 1. 把 getLevel 改成箭头函数
// 2. 把 add 改成箭头函数
// 3. 用 map + 箭头函数生成 ["[DOM]", ...]

View File

@@ -0,0 +1,24 @@
# 练习 4模块基础
## 目标
学会用 `export``import` 把不同文件连起来。
## 你要练什么
- `export`
- `import`
- `type="module"`
## 任务
- 从模块文件里导入课程数据和格式化函数
- 把结果渲染到页面
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/04-modules-basic/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/04-modules-basic/starter.js)
- [course-data.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/04-modules-basic/course-data.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/04-modules-basic/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/04-modules-basic/answer.js)

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模块基础</title>
</head>
<body>
<pre id="output"></pre>
<script type="module" src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
import { courses, formatCourse } from "./course-data.js";
const lines = courses.map((course) => formatCourse(course));
document.getElementById("output").textContent = lines.join("\n");

View File

@@ -0,0 +1,3 @@
export const courses = ["解构", "展开运算符", "Promise"];
export const formatCourse = (course) => `正在学习:${course}`;

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模块基础</title>
</head>
<body>
<pre id="output"></pre>
<script type="module" src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,3 @@
// 任务:
// 1. 从 ./course-data.js 导入 courses 和 formatCourse
// 2. 把格式化后的结果写入 #output

View File

@@ -0,0 +1,24 @@
# 练习 5Promise 基础
## 目标
学会用 Promise 表达异步结果。
## 你要练什么
- Promise
- `.then()`
- `.catch()`
## 任务
- 调用模拟请求函数
- 成功时渲染课程数据
- 失败时渲染错误状态
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/05-promise-basics/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/05-promise-basics/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/05-promise-basics/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/05-promise-basics/answer.js)

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promise 基础</title>
</head>
<body>
<p id="status">等待加载</p>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
function loadTracks() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["模板字符串", "模块化", "async/await"]);
}, 700);
});
}
const status = document.getElementById("status");
const output = document.getElementById("output");
status.textContent = "加载中...";
loadTracks()
.then((tracks) => {
status.textContent = "加载完成";
output.textContent = tracks.join("\n");
})
.catch(() => {
status.textContent = "加载失败";
});

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promise 基础</title>
</head>
<body>
<p id="status">等待加载</p>
<pre id="output"></pre>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
function loadTracks() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["模板字符串", "模块化", "async/await"]);
}, 700);
});
}
// 任务:
// 1. 调用 loadTracks()
// 2. 成功时更新状态和输出内容
// 3. 失败时更新状态

View File

@@ -0,0 +1,24 @@
# 练习 6async / await
## 目标
学会用 `async` / `await` 改写一个 Promise 流程。
## 你要练什么
- `async`
- `await`
- `try...catch`
## 任务
- 写一个 `async` 函数等待课程配置
- 成功时渲染结果
- 失败时更新状态
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/06-async-await/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/06-async-await/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/06-async-await/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/06-async-await/answer.js)

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>async / await</title>
</head>
<body>
<p id="status">等待加载</p>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
function loadConfig() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
title: "现代 JS 面板",
level: "进阶",
});
}, 800);
});
}
const status = document.getElementById("status");
const output = document.getElementById("output");
async function renderConfig() {
status.textContent = "加载中...";
try {
const config = await loadConfig();
status.textContent = "加载完成";
output.textContent = `${config.title} - ${config.level}`;
} catch (error) {
status.textContent = "加载失败";
}
}
renderConfig();

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>async / await</title>
</head>
<body>
<p id="status">等待加载</p>
<pre id="output"></pre>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,15 @@
function loadConfig() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
title: "现代 JS 面板",
level: "进阶",
});
}, 800);
});
}
// 任务:
// 1. 写一个 async 函数
// 2. 用 await 等待 loadConfig()
// 3. 用 try...catch 处理流程

View File

@@ -0,0 +1,23 @@
# 练习 7箭头函数里的 this
## 目标
理解箭头函数不会创建自己的 `this`
## 你要练什么
- 箭头函数
- `this`
- 定时器回调
## 任务
- 在对象方法里用箭头函数处理 `setTimeout`
- 让延迟输出仍然拿到当前对象的名称
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/07-arrow-this/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/07-arrow-this/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/07-arrow-this/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/07-arrow-this/answer.js)

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>箭头函数里的 this</title>
</head>
<body>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,10 @@
const trainer = {
name: "现代 JS 训练营",
report() {
setTimeout(() => {
document.getElementById("output").textContent = `当前模块:${this.name}`;
}, 500);
},
};
trainer.report();

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>箭头函数里的 this</title>
</head>
<body>
<pre id="output"></pre>
<script src="./starter.js"></script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
const trainer = {
name: "现代 JS 训练营",
report() {
// 任务:
// 1. 用 setTimeout
// 2. 在回调里用箭头函数读取 this.name
// 3. 把结果写入 #output
},
};
trainer.report();

View File

@@ -0,0 +1,26 @@
# 练习 8fetch 和 JSON
## 目标
补齐 `fetch()``res.json()` 这一层最常见的现代异步写法。
## 你要练什么
- `fetch()`
- `await`
- `res.json()`
- 接口数据渲染
## 任务
- 点击按钮后发起一次 `fetch`
- 等待返回的响应对象
- 调用 `res.json()` 解析 JSON
- 把课程标题和内容渲染到页面
## 文件
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/08-fetch-and-json/starter.html)
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/08-fetch-and-json/starter.js)
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/08-fetch-and-json/answer.html)
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/05-es6-plus/08-fetch-and-json/answer.js)

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>fetch 和 JSON</title>
</head>
<body>
<h1>fetch 和 JSON</h1>
<button id="load-btn" type="button">加载文章</button>
<p id="status">等待加载</p>
<pre id="output"></pre>
<script src="./answer.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More