今天吃啥APP开发实战:基于HarmonyOS API 24的随机选餐与美食收藏应用从零到一




每天纠结"吃什么"是当代人的世纪难题。本文用ArkUI写了一个帮你做决定的APP——从摇一摇随机选餐到美食收藏管理,完整记录开发全过程。
一、项目缘起:为什么做"今天吃啥"
1.1 世纪难题"吃什么"
据非正式统计,一个人平均每天花在"吃什么"这个问题上的决策时间约为10-15分钟,一年就是60-90小时——相当于整整4天。如果你有选择困难症,这个数字还要翻倍。
更糟糕的是,即使做出了决定,吃完之后常常会后悔:“早知道去另一家了”“上次那个更好吃”“又踩雷了”。
1.2 产品的三个核心价值
"今天吃啥"APP要解决的就是这个问题,它的核心价值在于:
1. 帮你做决定:摇一摇随机选餐,把选择交给命运(或者说交给算法),结束纠结。
2. 帮你记住好店:收藏附近美食和食堂菜谱,打造你的私人美食地图。
3. 帮你发现规律:通过分类筛选和评分系统,发现自己的口味偏好。
1.3 技术选型
| 技术维度 | 选择 | 理由 |
|---|---|---|
| 开发语言 | ArkTS | 静态类型、原生性能 |
| UI框架 | ArkUI | 声明式、组件丰富 |
| 数据持久化 | Preferences | KV存储,适合小型结构化数据 |
| 包结构 | @kit.ArkData | API 24标准包 |
| 开发工具 | DevEco Studio | 官方IDE |
二、需求分析与架构设计
2.1 功能需求
| 功能 | 优先级 | 说明 |
|---|---|---|
| 随机选餐 | P0 | 从美食库中随机选中一个,带滚动动画效果 |
| 按分类筛选 | P0 | 全部/附近美食/食堂菜谱/其他 |
| 美食列表 | P0 | 展示所有收藏,支持搜索和分类筛选 |
| 添加美食 | P0 | 名称/分类/地点/价格/评分/备注 |
| 编辑/删除 | P1 | 修改或删除已有条目 |
| 数据持久化 | P0 | Preferences存储,重启不丢失 |
| 默认数据 | P0 | 首次启动自动填充默认美食 |
2.2 架构设计
整个APP采用经典的三层架构:
┌─────────────────────────────────────────────────┐
│ 表现层 (UI Layer) │
│ 随机选餐视图 / 收藏夹列表视图 / 添加视图 │
│ 底部导航栏 / 删除确认弹窗 │
├─────────────────────────────────────────────────┤
│ 状态层 (State Layer) │
│ @State foods / filteredFoods / selectedCategory│
│ @State randomResult / isRolling / showAddDialog│
├─────────────────────────────────────────────────┤
│ 数据层 (Data Layer) │
│ Preferences 持久化存储 │
│ getDefaultFoods() 默认种子数据 │
│ loadData() / saveData() │
└─────────────────────────────────────────────────┘
2.3 视图导航结构
APP采用三个底部标签页,通过 currentView 状态切换:
build()
├── 顶部标题栏 (随机表情 + "今天吃啥")
├── 当前视图 (条件渲染)
│ ├── currentView === 'random' → buildRandomView()
│ ├── currentView === 'list' → buildListView()
│ └── currentView === 'add' → buildAddView()
├── 底部导航栏
│ ├── 🎲 随机选 → currentView = 'random'
│ ├── 📋 收藏夹 → currentView = 'list'
│ └── ➕ 添加 → currentView = 'add'
└── 删除确认弹窗 (当 showDeleteConfirm 时显示)
三、数据模型设计
3.1 类型定义
食物分类枚举:
enum FoodCategory {
FU_JIN = '附近美食',
SHI_TANG = '食堂菜谱',
QI_TA = '其他'
}
食物条目接口:
interface FoodItem {
id: number // 自增ID,唯一标识
name: string // 美食名称
category: FoodCategory // 分类
location: string // 地点/餐厅名
price: number // 价格(元),0表示未知
rating: number // 评分(1-5)
notes: string // 备注
}
3.2 默认种子数据
首次启动时,APP没有任何数据。为了让用户有"开箱即用"的体验,我们内置了20道默认美食:
private getDefaultFoods(): FoodItem[] {
return [
// 附近美食(10道)
{ id: 1, name: '兰州拉面', category: FU_JIN, location: '校门口左侧', price: 15, rating: 4, notes: '量足,牛肉多' },
{ id: 2, name: '黄焖鸡米饭', category: FU_JIN, location: '后街美食街', price: 18, rating: 5, notes: '汤汁浓郁' },
// ... 更多
];
}
种子数据的触发逻辑:在 loadData() 中判断,如果从 Preferences 读取到的数据为空数组,则写入默认数据:
async loadData(): Promise<void> {
const json = await this.pref.get(STORAGE_KEY, '[]') as string;
const raw: FoodItem[] = JSON.parse(json);
if (raw.length === 0) {
this.foods = this.getDefaultFoods();
this.nextId = this.foods.length + 1;
await this.saveData();
} else {
this.foods = raw.sort(...);
}
this.refresh();
}
这种"检测空数据→写入默认值"的模式,在需要预置数据的APP中非常实用。
3.3 @State变量的设计
| @State变量 | 类型 | 作用 | 更新时机 |
|---|---|---|---|
currentView |
string | 当前视图 | 点击底部导航 |
foods |
FoodItem[] | 全部美食数据 | 加载/增删改 |
filteredFoods |
FoodItem[] | 筛选后的数据 | 搜索/分类切换 |
selectedCategory |
string | 当前选中的分类 | 点击分类标签 |
searchText |
string | 搜索关键词 | 输入搜索框 |
randomResult |
string | 随机选餐结果 | 摇一摇完成 |
randomEmoji |
string | 结果表情 | 摇一摇完成 |
isRolling |
boolean | 是否正在滚动 | 开始/结束摇一摇 |
rollingText |
string | 滚动中的文字 | 每80ms更新 |
totalCount |
number | 总条数 | 刷新数据 |
fuJinCount |
number | 附近美食数 | 刷新数据 |
shiTangCount |
number | 食堂菜谱数 | 刷新数据 |
showAddDialog/edit* |
各种 | 添加/编辑表单 | 打开/关闭弹窗 |
showDeleteConfirm |
boolean | 删除确认显隐 | 点击删除/确认/取消 |
四、核心功能实现详解
4.1 摇一摇随机选餐
这是APP最核心的交互功能,也是用户打开APP最常使用的功能。
交互流程:
用户点击"🎲 摇一摇"按钮
→ 检查:是否有美食数据?(无则提示去添加)
→ 检查:是否正在滚动中?(防止连点)
→ isRolling = true
→ 每80ms触发一次定时器:
1. 从当前筛选列表中随机选一个
2. 更新 rollingText 显示
3. rollCount++
→ 当 rollCount > 20 时停止计时器:
1. 从筛选列表中随机选区最终结果
2. 更新 randomResult / randomEmoji
3. 拼接地点+价格+评分显示在副标题
4. isRolling = false
关键代码实现:
startRoll(): void {
if (this.isRolling) return;
if (this.foods.length === 0) {
this.randomResult = '还没有收藏美食,先去添加吧!';
this.randomEmoji = '😅';
return;
}
this.isRolling = true;
this.rollCount = 0;
const candidates = this.filteredFoods.length > 0 ? this.filteredFoods : this.foods;
this.rollTimer = setInterval(() => {
this.rollCount++;
const idx = Math.floor(Math.random() * candidates.length);
const item = candidates[idx];
this.rollingText = `${this.getCategoryEmoji(item.category)} ${item.name}`;
if (this.rollCount > 20) {
clearInterval(this.rollTimer);
this.isRolling = false;
const final = candidates[Math.floor(Math.random() * candidates.length)];
this.randomResult = final.name;
this.randomEmoji = this.getCategoryEmoji(final.category);
// 拼接详细信息显示
const detailParts: string[] = [];
if (final.location) detailParts.push(`📍 ${final.location}`);
if (final.price > 0) detailParts.push(`💰 ¥${final.price}`);
if (final.rating > 0) detailParts.push(`⭐ ${'★'.repeat(final.rating)}${'☆'.repeat(5 - final.rating)}`);
this.rollingText = detailParts.join(' · ');
}
}, 80);
}
设计要点:
- 滚动次数控制:20次约1.6秒,节奏适中,不会太长让人不耐烦,也不会太短没有悬念感。
- 最终结果随机:滚动过程中的显示是纯装饰,最终结果重新随机选取,保证公平性。
- 筛选联动:如果用户先筛选了分类(如"食堂菜谱"),摇一摇只会从该类目中选取,实现"想吃食堂就摇食堂"的精准需求。
4.2 分类筛选系统
分类筛选在"随机选餐"和"收藏夹"两个视图中都有用到。它由一组标签按钮构成:
@Builder
buildFilterChip(label: string) {
Text(label)
.fontSize(13)
.fontColor(this.selectedCategory === label ? '#FFFFFF' : '#666')
.backgroundColor(this.selectedCategory === label ? '#FF6B35' : '#F0F0F0')
.borderRadius(16)
.padding({ left: 14, right: 14, top: 5, bottom: 5 })
.margin({ right: 6 })
.onClick(() => {
this.selectedCategory = label;
this.refresh();
})
}
筛选逻辑在 applyFilter() 中实现:
applyFilter(): void {
let list = this.foods;
if (this.selectedCategory !== '全部') {
list = list.filter(f => f.category === this.selectedCategory);
}
if (this.searchText) {
const kw = this.searchText;
list = list.filter(f =>
f.name.includes(kw) || f.location.includes(kw) || f.notes.includes(kw)
);
}
this.filteredFoods = list;
}
筛选与摇一摇的联动:当用户选择了某个分类后,摇一摇只从该分类中选取——这背后是 startRoll() 中 const candidates = this.filteredFoods 的逻辑。筛选列表即是候选列表。
4.3 搜索功能
搜索功能实现在收藏夹视图顶部。用户输入关键词后,实时过滤美食列表:
状态更新:
TextInput({ placeholder: '搜索美食、地点...', text: this.searchText })
.onChange((val: string) => {
this.searchText = val;
this.applyFilter();
})
每次输入变化都会触发 applyFilter(),实时更新列表。这种"即时搜索"(Instant Search)模式在移动端已经成为标配,用户在输入时立刻看到结果,无需手动提交。
4.4 添加/编辑表单
添加和编辑共用同一个表单视图,通过 editId 区分模式:
editId === -1:添加模式,标题显示"📝 添加美食"editId !== -1:编辑模式,标题显示"💾 保存修改"
表单字段:
| 字段 | 组件 | 说明 |
|---|---|---|
| 名称 | TextInput | 必填,不能为空 |
| 分类 | 三个单选标签 | 附近美食/食堂菜谱/其他 |
| 地点 | TextInput | 选填 |
| 价格 | TextInput (Number) | 0表示未知 |
| 评分 | 5个可点击的星星 | 点击选择1-5星 |
| 备注 | TextInput | 选填 |
评分组件的实现:
Row() {
ForEach([1, 2, 3, 4, 5], (star: number) => {
Text(star <= this.editRating ? '⭐' : '☆')
.fontSize(24)
.onClick(() => { this.editRating = star; })
}, (star: number) => star.toString())
}
通过 star <= this.editRating 判断显示实心还是空心星星,点击时设置评分值,代码只有5行。
4.5 @State数组的不可变更新
在ArkUI中,@State 数组需要遵循"不可变更新"原则。以删除功能为例:
confirmDelete(): void {
// ❌ 错误:直接修改原数组不会触发UI更新
// this.foods.splice(index, 1);
// ✅ 正确:创建新数组替换
this.foods = this.foods.filter(f => f.id !== this.deleteId);
this.showDeleteConfirm = false;
this.saveData();
this.refresh();
}
filter 返回新数组,赋值给 this.foods 后ArkUI检测到引用变化,触发重新渲染。
同样地,在编辑功能中:
const old = this.foods[idx];
// 创建新对象替换数组中的元素
this.foods[idx] = {
id: old.id, // 保留原有ID
name: newName,
category: newCategory,
// ...其他属性
};
注意:这里修改的是 this.foods 数组中的元素对象。仅仅修改 this.foods[idx] 的引用并不会触发数组级别的重新渲染。需要确保 this.foods 本身也获得新的引用。但在上述代码中,直接赋值 this.foods[idx] = { ... } 确实修改了数组内部的元素,但数组自身的引用没有变化。
正确的做法应该是:
// 创建整个数组的新引用
const newFoods = [...this.foods]; // ❌ 展开运算符ArkTS不支持
// 安全做法:手动复制
const newFoods: FoodItem[] = [];
for (let i = 0; i < this.foods.length; i++) {
newFoods.push(this.foods[i]);
}
newFoods[idx] = { ... }; // 替换元素
this.foods = newFoods;
等等——之前的代码有Bug!
我重新审视了编辑功能中的代码:
this.foods[idx] = { id: old.id, name: ..., ... };
这行代码修改了数组内部元素,但 this.foods 的引用没有变化。在ArkUI中,修改数组元素不会触发UI更新。需要创建新的数组引用。
这是一个需要注意的ArkUI使用误区。正确的实现应该是:
const newFoods: FoodItem[] = [];
for (let i = 0; i < this.foods.length; i++) {
if (i === idx) {
newFoods.push({ id: old.id, name: this.editName, /* ... */ });
} else {
newFoods.push(this.foods[i]);
}
}
this.foods = newFoods;
但这里有一个有趣的现象:在 saveFood() 中,我们调用了 this.refresh(),而 refresh() 中调用了 this.applyFilter(),后者重新赋值了 this.filteredFoods 这个 @State 变量。所以即使 this.foods 没有触发渲染,this.filteredFoods 的变化仍然会触发UI更新。
这种"间接触发渲染"的方式虽然能工作,但不是最佳实践。更可靠的方式是确保每次修改 this.foods 时都创建新数组。
五、UI实现详解
5.1 整体视觉效果
APP采用暖色系的视觉风格,与"美食"主题匹配:
| 用途 | 色值 | 说明 |
|---|---|---|
| 主色调 | #FF6B35 |
橙色,食欲色 |
| 背景色 | #FFF8F0 |
米白,温暖 |
| 卡片背景 | #FFFFFF |
纯白 |
| 食堂菜谱标签 | #004E89 |
蓝色 |
| 附近美食标签 | #FF6B35 |
橙色 |
| 其他标签 | #1A936F |
绿色 |
| 评分星色 | #FFB800 |
金色 |
5.2 摇一摇视图(首页)
摇一摇视图是APP的首页,布局结构如下:
Column
├── Row: 统计标签 (🏪 附近X · 🍱 食堂X · 📌 共X)
├── Row: 分类筛选标签 (全部/附近美食/食堂菜谱/其他)
├── Column: (居中, layoutWeight=1)
│ ├── Text: 结果表情 (72fp 大号)
│ ├── Text: 结果文字/滚动文字
│ └── Text: 地点·价格·评分 (副标题)
└── Button: "🎲 摇一摇" 大按钮 (底部固定)
统计标签展示当前各类别的数量,让用户一打开APP就能了解自己收藏了多少美食。
分类筛选标签可以滚动,支持横向滑动查看更多。
摇一摇按钮使用大尺寸圆角按钮,带阴影,视觉上"想让人按下去"。
5.3 收藏夹视图
收藏夹视图的布局:
Column
├── Row: 搜索框 (🔍 输入框 + ✕ 清空)
├── Row: 分类筛选标签 (同首页)
├── List: 美食卡片列表 (可滚动)
│ ├── ListItem: 美食卡片1
│ ├── ListItem: 美食卡片2
│ └── ...
└── (空状态提示:没有找到匹配的美食)
美食卡片设计:
Row
├── Text: 分类图标 (🏪/🍱/🍽️, 28fp)
├── Column: (layoutWeight=1)
│ ├── Row: 名称 (17fp) + 分类标签 (小标签)
│ ├── Row: 📍 地点 · 💰 ¥价格
│ ├── Text: 评分星星 (★)
│ └── Text: 备注 (可选, 最多1行)
└── Row: ✏️ 编辑 · 🗑️ 删除
卡片点击行为:点击卡片直接跳转到摇一摇视图,并将该美食设置为当前选中结果。这个设计的意图是——用户在浏览收藏时看到某个想吃的,点击即可"锁定"它,然后去摇一摇确认。但实际上,点击卡片的逻辑是直接将当前美食显示在摇一摇结果区,用户可以确认这就是今天想吃的。
5.4 添加视图
添加视图是一个表单页面:
Column
├── Text: "📝 添加美食" (标题)
├── Scroll
│ └── Column
│ ├── Row: 名称 (TextInput)
│ ├── Row: 分类 (三个单选标签)
│ ├── Row: 地点 (TextInput)
│ ├── Row: 价格 (TextInput Number + "元")
│ ├── Row: 评分 (5个可点击星星)
│ ├── Row: 备注 (TextInput)
│ └── Button: "✅ 添加" 或 "💾 保存修改"
5.5 删除确认弹窗
删除操作需要用户二次确认,防止误删:
Column (宽度80%, 圆角16, 白色背景)
├── Text: "🗑️ 确认删除" (标题)
├── Text: "删除后无法恢复,确定要删除吗?" (提示)
└── Row
├── Button: "取消" (灰色)
└── Button: "删除" (红色)
六、踩坑合集:今天吃啥APP中的3个关键问题
坑1:@State数组元素修改不触发渲染
问题:以下代码不会触发UI更新:
this.foods[idx] = newItem; // ❌ 数组引用不变
原因:ArkUI对 @State 数组的检测基于引用比较(reference comparison)。修改数组中的某个元素不会改变数组本身的引用地址。
解决方案:每次修改数组时创建新引用:
const newFoods: FoodItem[] = [];
for (let i = 0; i < this.foods.length; i++) {
newFoods.push(this.foods[i]);
}
newFoods[idx] = { id: old.id, name: newName, /* ... */ };
this.foods = newFoods;
注意:在ArkTS中不可以使用展开运算符 [...arr] 来复制数组,必须使用 for 循环手动复制。
坑2:setInterval的timer类型
问题:在ArkTS中,setInterval 返回的 timer 类型与JavaScript不同。
JavaScript:setInterval 返回 number(浏览器环境)或 NodeJS.Timeout(Node环境)。
ArkTS:setInterval 返回 number。可以使用 number 类型来存储 timer:
private rollTimer: number = -1;
清理 timer:使用 clearInterval() 时注意参数类型匹配:
clearInterval(this.rollTimer);
如果没有正确清理 timer,组件销毁后 timer 仍在运行,可能导致内存泄漏或访问已销毁组件的状态。
坑3:分类标签的滚动能力
问题:分类标签行(全部/附近美食/食堂菜谱/其他)在窄屏手机上可能显示不全。
解决方案:使用 .scrollable() 属性使行支持横向滚动:
Row() {
this.buildFilterChip('全部')
this.buildFilterChip('附近美食')
this.buildFilterChip('食堂菜谱')
this.buildFilterChip('其他')
}
.width('100%')
.padding(...)
.scrollable(ScrollAlignment.START)
但在实际测试中,Row 组件的 .scrollable() 方法在某些ArkUI版本中可能不直接支持。如果有兼容性问题,可以将标签行放在 Scroll 组件中:
Scroll() {
Row() {
// 标签内容
}
}
.scrollable(ScrollDirection.HORIZONTAL)
七、数据持久化与状态恢复
7.1 Preferences的读写流程
// 写入
async saveData(): Promise<void> {
if (this.pref) {
await this.pref.put(STORAGE_KEY, JSON.stringify(this.foods));
await this.pref.flush();
}
}
// 读取
async loadData(): Promise<void> {
const json = await this.pref.get(STORAGE_KEY, '[]') as string;
const raw: FoodItem[] = JSON.parse(json);
// 处理数据...
}
关键点:
put+flush:put将数据写入内存缓存,flush确保写入磁盘get的第二个参数:当键不存在时返回的默认值- JSON序列化:对象数组需要序列化为字符串存储,读取时反序列化
7.2 数据加载的生命周期
async aboutToAppear(): Promise<void> {
await this.loadData();
}
aboutToAppear 是组件的生命周期回调,在组件即将显示时调用。在这里加载数据,确保用户看到界面时数据已经就绪。
7.3 ID自增策略
private nextId: number = 1;
// 加载时更新
for (const f of this.foods) {
if (f.id >= this.nextId) this.nextId = f.id + 1;
}
// 新增时使用
const food: FoodItem = {
id: this.nextId++,
// ...
};
这种"最大ID+1"的策略简单可靠,不会出现ID冲突。
八、项目结构和代码统计
8.1 文件结构
Index.ets (~730行)
├── 类型定义 (~20行)
│ ├── enum FoodCategory
│ └── interface FoodItem
│
├── 组件定义 (~700行)
│ ├── @State变量及private成员 (~30行)
│ ├── 数据加载与持久化 (~100行)
│ │ ├── loadData()
│ │ ├── getDefaultFoods() ← 20道默认美食
│ │ ├── saveData()
│ │ └── refresh() / applyFilter()
│ ├── 随机选餐逻辑 (~40行)
│ │ └── startRoll()
│ ├── CRUD操作 (~70行)
│ │ ├── openAddDialog / openEditDialog
│ │ ├── saveFood
│ │ └── requestDelete / confirmDelete
│ ├── UI辅助方法 (~20行)
│ │ ├── getCategoryEmoji / getRatingStars
│ │ └── getCategoryTagColor
│ ├── build() 入口 (~10行)
│ ├── @Builder buildBottomNav (~30行)
│ ├── @Builder buildRandomView (~100行)
│ ├── @Builder buildListView (~120行)
│ ├── @Builder buildAddView (~130行)
│ ├── @Builder buildFoodCard (~50行)
│ ├── @Builder buildFilterChip (~20行)
│ └── @Builder buildDeleteConfirmDialog (~30行)
8.2 代码量分布
| 模块 | 行数 | 占比 |
|---|---|---|
| 类型定义 | ~20 | 3% |
| 数据层(加载/持久化/CRUD) | ~170 | 23% |
| UI层(@Builder视图) | ~520 | 71% |
| 工具方法 | ~20 | 3% |
UI代码占主导是ArkUI应用的典型特征。这也符合声明式UI的理念——UI即代码,代码即UI。
九、总结与展望
9.1 项目复盘
"今天吃啥"APP的开发周期约半天,代码量约730行,实现了三个核心功能:
| 功能 | 实现方式 | 用户体验 |
|---|---|---|
| 随机选餐 | setInterval + 随机选取 + 滚动动画 | 🎲 有点紧张感,结果有仪式感 |
| 美食收藏 | 增删改查 + Preferences持久化 | 📋 构建私人美食地图 |
| 分类筛选 | 标签 + 搜索 + 数据过滤联动 | 🔍 精准定位想吃的类型 |
9.2 用户使用场景
- 中午12:00:打开APP → 点击"摇一摇" → 选中"黄焖鸡米饭" → 走,去吃!
- 晚上发现新店:打开APP → 切换到"添加" → 记录店名、价格、评分
- 周末想换口味:筛选"其他"分类 → 摇一摇 → 选中"韩式炸鸡" → 点外卖!
9.3 可扩展方向
1. 组队模式
创建群组,多人一起摇一摇,投票决定最终吃什么。
2. 外卖比价
接入外卖平台的公开数据,展示同一道菜在不同平台的价格。
3. 口味分析
根据历史评分和点评,分析用户的口味偏好(偏辣/偏甜/偏清淡)。
4. 智能推荐
记录每次选择后的满意度,用简单的协同过滤算法推荐更精准的美食。
5. 地图集成
通过 @kit.LocationKit 获取当前位置,推荐附近收藏的美食。
附录:完整API清单
@kit.ArkData
| API | 用途 |
|---|---|
preferences.getPreferences(context, name) |
获取/创建偏好数据库 |
preferences.Preferences.get(key, def) |
读取值 |
preferences.Preferences.put(key, value) |
写入值 |
preferences.Preferences.flush() |
刷入磁盘 |
ArkUI组件
| 组件 | 用途 |
|---|---|
Column |
垂直布局 |
Row |
水平布局 |
Text |
文本显示 |
TextInput |
文本输入 |
Button |
按钮 |
List |
列表容器 |
ListItem |
列表项 |
Scroll |
滚动容器 |
ForEach |
循环渲染 |
ArkUI属性方法
| 方法 | 用途 |
|---|---|
.fontSize() .fontWeight() .fontColor() |
字体控制 |
.backgroundColor() .borderRadius() |
背景与圆角 |
.shadow() |
阴影 |
.layoutWeight() |
弹性布局权重 |
.scrollable() |
滚动能力 |
.maxLines() .textOverflow() |
文本截断 |
.opacity() |
透明度 |
.onClick() .onChange() |
事件监听 |
全局JavaScript API
| API | 用途 | ArkTS兼容 |
|---|---|---|
Math.random() |
随机数 | ✅ |
Math.floor() |
向下取整 | ✅ |
JSON.parse() |
JSON解析 | ✅ |
JSON.stringify() |
JSON序列化 | ✅ |
setInterval() |
定时器 | ✅ (返回number) |
clearInterval() |
清除定时器 | ✅ |
Array.push() |
数组追加 | ✅ |
Array.filter() |
数组过滤 | ✅ |
Array.sort() |
数组排序 | ✅ |
Array.forEach() |
数组遍历 | ✅ |
更多推荐

所有评论(0)