零基础鸿蒙应用开发第八节:流程控制之循环语句
【学习目标】
- 理解循环语句的核心作用,掌握“重复执行相同/相似逻辑”的编程思维
- 掌握
for循环的核心语法、计数器逻辑及典型场景(数组遍历、固定次数循环) - 掌握
for...of循环的简洁遍历方式,学会处理数组/字符串等可迭代对象(含索引获取技巧) - 掌握
while/do-while循环的语法差异,理解“先判断后执行”与“先执行后判断”的适用场景 - 掌握
break/continue关键字的作用,学会优化循环流程(提前终止、过滤无效迭代) - 能结合实际业务场景(数据批量处理、条件循环、迭代遍历)选择合适的循环语句,规避死循环
【学习重点】
for循环的计数器需明确“起始值、终止条件、步长”,避免计数器越界或死循环for...of适用于可迭代对象遍历(数组/字符串),获取索引需配合Array.prototype.entries()while循环需确保“终止条件最终为false”,do-while至少执行一次循环体(即使条件为false)break终止整个循环,continue跳过当前迭代(仅终止单次循环),两者作用域为“最近的循环”- 循环选型原则:固定次数循环用
for,可迭代对象遍历用for...of,条件循环用while/do-while
一、工程结构
本节我们将创建名为FlowControlLoopDemo的工程,基于 鸿蒙5.0(API12) 开发,使用 DevEco Studio 6.0+ 工具,项目结构目录如下:
FlowControlLoopDemo
├── AppScope # 应用全局配置
├── entry
│ ├── src
│ │ ├── main
│ │ │ ├── ets
│ │ │ │ ├── entryability # 应用入口(无需修改)
│ │ │ │ ├── pages
│ │ │ │ │ └── Index.ets # 测试页面(调用循环函数)
│ │ │ │ └── utils
│ │ │ │ └── FlowControlTest.ets # 循环核心代码
│ │ │ ├── resources # 静态资源(无需修改)
│ │ │ └── module.json5 # 模块配置(无需修改)
│ │ └── ...(其他目录默认即可)
└── ...(其他目录默认即可)
前序章节内容我们已经对于工程的创建,新建文件夹以及代码文件足够熟练。后续内容不再重复讲如何创建工程以及文件。不熟悉的从开篇看。
二、循环语句核心概念:为什么需要循环?
循环语句解决“重复执行相同/相似逻辑”的问题,是处理批量数据、重复操作的核心工具。
生活场景类比:
- 计算1-100的累加和(重复“加数字”操作100次);
- 遍历购物车列表计算总价(重复“加单价”操作,次数=商品数量);
- 验证用户输入直到合法(重复“输入校验”,直到条件满足);
- 打印5遍“Hello ArkTS”(固定次数重复输出)。
编程中,循环语句(for/for...of/while/do-while)就是实现这类“重复操作”的核心语法。
三、for循环:固定次数的精准循环
for循环适用于已知循环次数的场景,核心是“计数器三要素”(初始化、终止条件、步长更新),是最常用的循环类型。
3.1 基础语法
for (初始化计数器; 终止条件; 计数器更新) {
// 循环体:终止条件为true时执行的代码
}
- 初始化计数器:仅执行1次,定义循环起始值(如
let i = 0); - 终止条件:每次循环前判断,返回布尔值,为
false时终止循环; - 计数器更新:每次循环体执行完后执行,控制循环步长(如
i++/i += 2); - 作用域:推荐用
let定义计数器(块级作用域),避免var的全局污染(简单了解,后边作用域会重点讲)。
3.2 代码示例
示例1:基础for循环(固定次数输出)
/**
* 测试基础for循环:固定次数输出(打印5遍Hello ArkTS)
*/
export function testForBasic(): void {
console.log(`\n========== 基础for循环(固定次数输出) ==========`);
// 三要素:i从0开始,i<5时循环,每次i+1
for (let i = 0; i < 5; i++) {
console.log(`第${i+1}遍:Hello ArkTS`);
}
}
运行效果:
========== 基础for循环(固定次数输出) ==========
第1遍:Hello ArkTS
第2遍:Hello ArkTS
第3遍:Hello ArkTS
第4遍:Hello ArkTS
第5遍:Hello ArkTS
示例2:for循环(数组遍历+购物车总价计算)
/**
* 测试for循环:数组遍历(计算购物车商品总价)
*/
export function testForArray(): void {
console.log(`\n========== for循环(数组遍历-购物车总价) ==========`);
// 模拟购物车商品单价
const cartPrices: number[] = [99, 199, 299, 49];
let totalPrice: number = 0;
// 遍历数组:i从0到数组长度-1(避免索引越界,数组下标默认从0开始)
for (let i = 0; i < cartPrices.length; i++) {
totalPrice += cartPrices[i];
console.log(`第${i+1}个商品单价:${cartPrices[i]},当前总价:${totalPrice}`);
}
console.log(`购物车总价:${totalPrice}`);
}
运行效果:
========== for循环(数组遍历-购物车总价) ==========
第1个商品单价:99,当前总价:99
第2个商品单价:199,当前总价:298
第3个商品单价:299,当前总价:597
第4个商品单价:49,当前总价:646
购物车总价:646
3.3 新手易错点
- 索引越界:终止条件写
i <= cartPrices.length(数组最大索引为length-1);ArkTS中数组越界不会直接闪退,但会返回undefined,导致后续逻辑错误(如计算NaN),同样需要严格避免(其他开发语言中越界可能导致程序闪退); - 死循环:遗漏
i++或终止条件永远为true(如i > -1),循环永不停止; - 步长矛盾:如
i=10; i>0; i++(步长与终止条件反向,直接死循环); - 计数器作用域:javascript中用
var i = 0定义,循环结束后i仍可访问,易引发后续错误;ArkTS并不支持var声明变量(作为了解),只能使用let/const。
四、for...of循环:可迭代对象的遍历
for...of适用于可迭代对象(数组、字符串、Set、Map等),无需手动管理计数器,代码更简洁。
4.1 基础语法
for (const 元素变量 of 可迭代对象) {
// 循环体:依次遍历每个元素
}
- 元素变量:推荐用
const(遍历中值不变),需修改则用let; - 可迭代对象:普通对象(
{})不可直接遍历,需转成数组(如Object.values(obj)); - 索引获取:需配合
Array.prototype.entries(),ArkTS不支持数组解构的简写形式(for (const [index,fruit] of fruits.entries())),需通过数组下标访问索引-元素对。
4.2 代码示例
示例1:基础for...of(购物车计算)
/**
* 测试for...of循环:基础数组遍历(购物车总价)
*/
export function testForOfBasic(): void {
console.log(`\n========== for...of循环(基础数组遍历) ==========`);
const cartPrices: number[] = [99, 199, 299, 49];
let totalPrice: number = 0;
let index: number = 0; // 手动维护索引(可选)
for (const price of cartPrices) {
totalPrice += price;
index++;
console.log(`第${index}个商品单价:${price},当前总价:${totalPrice}`);
}
console.log(`购物车总价:${totalPrice}`);
}
运行效果:与testForArray完全一致。
示例2:for...of(带索引+字符串遍历)
/**
* 测试for...of循环:带索引遍历+字符串遍历
*/
export function testForOfIndex(): void {
console.log(`\n========== for...of循环(带索引+字符串遍历) ==========`);
// 1. 数组带索引遍历(ArkTS推荐写法)
const fruits: string[] = ["苹果", "香蕉", "橙子"];
// entries()返回迭代器,每个元素是[index, value]数组,通过下标访问
for (const item of fruits.entries()) {
console.log(`索引${item[0]}:${item[1]}`);
}
// 2. 字符串遍历(按字符拆分)
const str: string = "ArkTS";
console.log(`\n字符串"${str}"字符遍历:`);
for (const char of str) {
console.log(`字符:${char}`);
}
}
运行效果:
========== for...of循环(带索引+字符串遍历) ==========
索引0:苹果
索引1:香蕉
索引2:橙子
字符串"ArkTS"字符遍历:
字符:A
字符:r
字符:k
字符:T
字符:S
4.3 新手易错点
- 遍历普通对象:直接用
for...of遍历{name: "张三"}(普通对象不可迭代); - 修改const元素:用
const price却尝试price = 100(需改为let); - 空值遍历:遍历
undefined/null(非可迭代对象,直接报错)。
五、while/do-while循环:条件驱动的循环
适用于未知循环次数的场景,核心是“条件表达式”而非固定次数。
5.1 while循环(先判断后执行)
语法
初始化循环变量;
while (终止条件) {
// 循环体:条件为true时执行
更新循环变量; // 必须更新,否则死循环
}
代码示例(用户输入验证)
/**
* 测试while循环:用户输入验证(模拟直到输入合法数字)
*/
export function testWhileBasic(): void {
console.log(`\n========== while循环(用户输入验证) ==========`);
// 模拟用户输入(实际为UI输入框取值)
let userInput: string | number = "abc";
let validNum: number = 0;
// 循环条件:输入不是数字 或 数字小于0
while (isNaN(Number(userInput)) || Number(userInput) < 0) {
// 输入不符合要求给出提示
console.log(`当前输入:${userInput} → 无效,请输入非负数字!`);
// 模拟用户修正输入(第3次输入合法值)
if (userInput === "abc") {
// 第二次修改为 "-10"
userInput = "-10";
} else if (userInput === "-10") {
// 第三次修改为 "99"
userInput = "99";
}
}
validNum = Number(userInput);
console.log(`输入合法:${validNum},验证通过!`);
}
运行效果:
========== while循环(用户输入验证) ==========
当前输入:abc → 无效,请输入非负数字!
当前输入:-10 → 无效,请输入非负数字!
输入合法:99,验证通过!
5.2 do-while循环(先执行后判断)
语法
初始化循环变量;
do {
// 循环体:至少执行1次
更新循环变量;
} while (终止条件);
代码示例(登录重试)
/**
* 测试do-while循环:登录重试(至少执行1次验证)
*/
export function testDoWhileBasic(): void {
console.log(`\n========== do-while循环(登录重试) ==========`);
let retryCount: number = 0;
const maxRetry: number = 3;
let isLoginSuccess: boolean = false;
// 至少执行1次登录验证
do {
retryCount++;
console.log(`第${retryCount}次登录验证...`);
// 模拟第3次登录成功
if (retryCount === 3) {
isLoginSuccess = true;
}
} while (!isLoginSuccess && retryCount < maxRetry);
if (isLoginSuccess) {
console.log("登录成功!");
} else {
console.log("重试次数耗尽,登录失败!");
}
}
运行效果:
========== do-while循环(登录重试) ==========
第1次登录验证...
第2次登录验证...
第3次登录验证...
登录成功!
5.3 核心差异
| 特性 | while循环 |
do-while循环 |
|---|---|---|
| 执行顺序 | 先判断,后执行 | 先执行,后判断 |
| 执行次数 | 可能0次(初始条件为false) | 至少1次 |
| 适用场景 | 不确定是否需要执行 | 必须执行至少1次 |
5.4 新手易错点
- 死循环:
while循环中遗漏更新变量(如忘记修改userInput); - 分号遗漏:
do-while末尾忘记写;(语法报错); - 条件永远为true:如
while (1)(数字1会被隐式转换为布尔值true,等价于while (true),会触发死循环)。
六、break/continue:循环流程控制
用于手动干预循环执行,break终止整个循环,continue跳过当前迭代。
6.1 核心作用
| 关键字 | 作用 | 适用场景 |
|---|---|---|
break |
立即终止整个循环,跳出循环体 | 找到目标值后提前终止循环(如查找第一个符合条件的元素) |
continue |
跳过当前迭代,进入下一次循环 | 过滤无效数据(如跳过空值/负数),仅处理有效值 |
6.2 代码示例
/**
* 测试break/continue:优化循环流程
*/
export function testBreakContinue(): void {
console.log(`\n========== break/continue(循环流程控制) ==========`);
const nums: number[] = [1, 3, 5, 7, 8, 9, 10];
// 1. break:找到第一个偶数后终止循环
console.log("--- break示例(找第一个偶数) ---");
for (const num of nums) {
if (num % 2 === 0) {
console.log(`找到第一个偶数:${num},终止循环`);
break;
}
console.log(`当前数字:${num}(奇数)`);
}
// 2. continue:仅打印偶数,跳过奇数
console.log("\n--- continue示例(仅打印偶数) ---");
for (const num of nums) {
if (num % 2 !== 0) {
continue; // 跳过奇数
}
console.log(`偶数:${num}`);
}
}
运行效果:
========== break/continue(循环流程控制) ==========
--- break示例(找第一个偶数) ---
当前数字:1(奇数)
当前数字:3(奇数)
当前数字:5(奇数)
当前数字:7(奇数)
找到第一个偶数:8,终止循环
--- continue示例(仅打印偶数) ---
偶数:8
偶数:10
七、循环语句选型对比
| 循环类型 | 核心适用场景 | 优点 | 缺点 |
|---|---|---|---|
for |
固定次数、数组索引遍历 | 精准控制次数/步长 | 代码稍繁琐,需管理计数器 |
for...of |
可迭代对象遍历(无索引需求) | 代码简洁,无需管理计数器 | 无法直接获取索引,不支持普通对象 |
while |
未知次数、条件循环 | 灵活,仅依赖条件表达式 | 易遗漏变量更新导致死循环 |
do-while |
必须执行至少1次的条件循环 | 保证至少执行1次 | 即使条件为false也会执行1次 |
八、内容总结
1. 核心选型原则
- 固定次数循环 →
for(如数组索引遍历、固定次数输出); - 可迭代对象遍历 →
for...of(如数组/字符串遍历,无索引需求); - 未知次数、可能不执行 →
while(如输入验证); - 未知次数、必须执行1次 →
do-while(如登录重试)。
2. 新手必避3个坑
- 死循环:确保循环变量最终能让终止条件为
false; - 索引越界:
for循环终止条件用i < 数组长度; - 类型错误:
for...of不遍历普通对象,while条件必须是布尔值。
3. 最佳实践
- 计数器用
let(块级作用域); for...of遍历数组优先用const;while/do-while先初始化变量,再写循环;- 循环内及时用
break终止(如找到目标值后),提升性能。
九、测试页面
// pages/Index.ets
import { allTest } from '../utils/FlowControlTest';
@Entry
@Component
struct Index {
// 页面加载时执行所有测试函数
aboutToAppear(): void {
allTest();
}
build() {
Column() {
Text("流程控制(下)——循环语句")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Text("运行日志请查看控制台")
.fontSize(16)
.margin(20)
.fontColor(Color.Grey)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
十、代码仓库
- 工程名称:FlowControlLoopDemo
- 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
十一、下节预告
下一节我们将进入流程控制综合实战,告别纯业务场景,用3个有趣又实用的经典编程案例,把前6节的核心知识点(循环+分支+数组+运算符)融会贯通,全程聚焦“知识点组合运用”,而非单纯堆砌代码,核心包括:
实战1:猜数字小游戏(入门级:while/do-while + if-else)
- 巩固知识:do-while循环(至少执行1次)、if-else分支(判断猜大/猜小/猜对)、随机数生成(Number内置对象)、break循环终止、计数器控制次数限制;
- 玩法设计:程序随机生成1-100的整数,用户最多猜7次,每次输入后提示“猜大了”“猜小了”,猜对或次数用尽后终止游戏,输出结果。
实战2:打印99乘法表(进阶级:嵌套for循环 + 字符串处理)
- 巩固知识:嵌套for循环(外层控行、内层控列)、条件分支(控制列数避免重复)、字符串拼接/模板字符串(格式化输出)、算术运算符(乘法运算);
- 输出效果:按三角格式打印1-9乘法表,格式对齐,直观呈现嵌套循环的逻辑。
实战3:生成杨辉三角(提升级:二维数组 + 多层循环)
- 巩固知识:二维数组的创建与遍历、多层for循环(行/列逻辑控制)、条件分支(边界值处理)、算术运算符(相邻元素求和)、格式化输出;
- 输出效果:生成指定行数的杨辉三角(如6行),清晰展示数组与循环的组合运用,为后续复杂数据处理打基础。
通过下节实战,你将不再孤立记忆语法,而是学会“根据需求选工具”——比如用do-while处理“至少执行1次”的场景,用嵌套for循环处理“多行多列”的结构,用二维数组存储复杂数据,真正掌握流程控制的核心思维,为后续函数封装、鸿蒙组件开发打下坚实基础。
浙公网安备 33010602011771号