跳到主要内容

第三节 今天吃什么实践

【任务描述】

在软件工程中,利用算法解决用户的“选择困难症”是一项极具实用价值的交互体验设计。本节任务要求学习者开发一款名为“今天吃什么”的在线餐饮决策辅助系统。 学习者需深刻理解 JavaScript 中的 Math 数学对象机制,通过伪随机数发生器(PRNG)实现对底层数组(Array)的高频随机寻址。同时,综合调度 setIntervalsetTimeout 定时器,结合对 DOM 节点的动态创建(createElement)与安全销毁(remove),打造出一个兼具流畅逻辑与绚丽视觉反馈的单页客户端应用程序。

【架构分析与核心 API 调度机制】

1. 有限状态机(FSM)与事件流驱动 系统的运行与停止,本质上是一个最基础的“有限状态机(Finite-State Machine)”。我们需要在内存中开辟一个全局标识符(如 Run 变量),用以记录系统当前的生命周期(00 代表待机,11 代表运行)。用户的点击事件(click)将作为触发器,不断驱动状态机在两种模式间发生跃迁。

2. 伪随机数算法与数组寻址 计算机底层无法产生绝对的随机数。我们将利用 Math.random() 获取 [0,1)[0, 1) 区间的浮点数,再乘以数据源(数组)的总长度,最后配合 Math.floor() 执行向下取整,从而计算出绝对安全的数组动态索引,实现菜品的等概率随机抽取。

【任务实施:决策系统的工程化构建】

任务一:视图拓扑挂载与 DOM 内存映射

首先,在 HTML 视图层搭建系统的基础 UI 骨架,并预留用于注入动态数据的空白容器。

<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>业务辅助系统 - 今天吃什么决策器</title>
<link rel="stylesheet" href="https://www.ytwes.com/web/css/6-1.min.css">
</head>
<body>
<div class="song_4_04_10">
<div class="title">今天吃什么?</div>
<div class="Item"></div>
<div class="FixedMainBtn">开始选</div>
<div><img src="img/fixedimg.gif" alt="随机决策助手动画"></div>
</div>

<script>
// JS 业务逻辑将在此处展开
</script>
</body>
</html>

随后进入 <script> 逻辑域。利用 document.querySelector 高阶 API,将 HTML 物理节点精准捕获并映射为内存中的常数对象(const),同时初始化数据源与状态机标识。

// 1. 初始化状态机标识(0: 待机态, 1: 运行态)
let Run = 0;
// 2. 声明全局定时器指针
let Timer;

// 3. 建立 DOM 节点至内存变量的绑定映射
const title = document.querySelector(".title");
const FixedMainBtn = document.querySelector(".FixedMainBtn");
const itemElement = document.querySelector(".Item");

// 4. 构建底层数据基座(业务数据字典)
const List = [
"酱烧饼",
"香辣锅",
"黄焖鸡米饭",
"兰州拉面",
"麦当劳",
"重庆小面",
"螺蛳粉",
];

任务二:状态机切换与高频轮询调度

为触发按钮(FixedMainBtn)挂载单击(click)事件侦听器。在回调闭包内部,我们通过拦截 Run 变量的值来执行业务分流。

// 挂载事件驱动流
FixedMainBtn.addEventListener("click", function () {
// 逻辑分支 A:若当前为待机态,则执行启动协议
if (!Run) {
title.innerHTML = "今天吃什么?";
this.innerHTML = "给我停下来"; // 按钮视觉反馈

// 开启高频轮询引擎:每 70 毫秒执行一次寻址渲染
Timer = setInterval(function () {
// 核心伪随机算法:获取安全数组索引
const randomIndex = Math.floor(Math.random() * List.length);
const randomFood = List[randomIndex];

// 将抽取到的数据实时推入 DOM 渲染树
itemElement.innerHTML = randomFood;

// 调用图形粒子特效生成器(见任务三)
generateParticleEffect(randomFood);
}, 70);

// 状态机跃迁至运行态
Run = 1;
}
// 逻辑分支 B:若当前为运行态,则执行制动协议
else {
title.innerHTML = "就吃这个了"; // 锁定视觉反馈
this.innerHTML = "换一个";

// 核心内存操作:精准销毁定时器线程,强制冻结数据渲染
clearInterval(Timer);

// 状态机重置为待机态
Run = 0;
}
});

任务三:动态节点池与 V8 引擎垃圾回收机制

为了增强视觉表现力,我们要求在文字滚动期间,屏幕周围随机生成飘落的食物名称(粒子特效)。 这要求我们在内存中通过 document.createElement() 凭空制造 DOM 节点,赋予其随机的物理坐标与透明度,并利用 appendChild() 强行将其推入浏览器的渲染树中。

在前端工程中,毫无节制地生成 DOM 节点是极度危险的操作,会导致系统内存被迅速榨干,甚至引发浏览器内核崩溃。我们在下方的代码中,不仅创建了节点,更利用 setTimeout 配合 remove() API,在动画结束后强制命令浏览器进行垃圾回收(Garbage Collection)。这体现了软件工程师对计算资源的极度克制与敬畏,是“绿色计算”理念在微观代码层面的生动实践。

// 图形粒子特效生成器函数封装
function generateParticleEffect(randomFood) {
// 1. 在内存中凭空开辟一个新的 div 实体
const foodElement = document.createElement('div');
foodElement.className = 'random-food'; // 注入预设的 CSS 行为类
foodElement.textContent = randomFood;

// 2. 利用 Math 算法推演随机几何坐标(规避边缘溢出)
foodElement.style.top = Math.ceil(Math.random() * (window.innerHeight - 50)) + 'px';
foodElement.style.left = Math.ceil(Math.random() * (window.innerWidth - 50)) + 'px';

// 3. 随机混入 Alpha 透明度通道与自适应字号
foodElement.style.color = `rgba(0, 0, 0, ${Math.random()})`;
foodElement.style.fontSize = Math.ceil(Math.random() * (22 - 12) + 12) + 'px';

// 4. 将游离态的 DOM 节点正式挂载至宿主文档树
document.body.appendChild(foodElement);

// 5. 生命周期调度:90 毫秒后注入 CSS 淡出触发类
setTimeout(() => {
foodElement.classList.add('fade-out');
}, 90);

// 6. 生命周期终结:500 毫秒后强制剥离节点,释放物理内存
setTimeout(() => {
foodElement.remove();
}, 500);
}

【课程思政】

代码的克制与内存泄漏防范

在前端工程中,毫无节制地生成 DOM 节点是极度危险的操作,会导致系统内存被迅速榨干,甚至引发浏览器内核崩溃。利用 setTimeout 配合 remove() API 在动画结束后强制命令浏览器进行垃圾回收(Garbage Collection),体现了软件工程师对计算资源的极度克制与敬畏,是“绿色计算”理念在微观代码层面的生动实践。

【工程验收与视图验证】

保存资产配置并在浏览器中激活环境。 单击“开始选”按钮,严密观察:

  1. 主渲染区域:菜品文本是否以每 70ms 的高频帧率进行无缝切换(验证 setIntervalMath.random 的协同工作能力)。
  2. 粒子特效域:全屏是否如期出现字号、透明度各异的随机文字漂浮层。按 F12 唤出开发者工具(Elements 面板),实时观察 <body> 标签内部。确认生成的 <div class="random-food"> 节点是否在 500 毫秒后被精准剥离(验证内存回收逻辑的健壮性)。
  3. 状态机制动:再次单击按钮,确认所有动画是否被瞬间冻结,标题文本是否如期变更为“就吃这个了”。