Day 03 - 条件控制

目标:掌握ArkTS的条件语句、分支控制和类型守卫模式
预计时间:2-3小时


第一部分:if 语句

1.1 单分支 if

语法与C++相同:当条件为 true 时执行代码块。

// IoT场景:温度超限报警
let temperature: number = 85.5;

if (temperature > 80.0) {
  console.log(`温度警告:当前${temperature}°C,超过阈值80°C`);
}

⚠️ ArkTS要求:if 后的条件表达式必须是 boolean 类型或可隐式转换为 boolean 的类型


1.2 双分支 if-else

// IoT场景:设备在线状态判断
let isOnline: boolean = false;

if (isOnline) {
  console.log("设备在线,可以发送控制指令");
} else {
  console.log("设备离线,无法通信");
}

// 实际应用:设置默认值
let inputName: string = "";
let deviceName: string;

if (inputName !== "") {
  deviceName = inputName;
} else {
  deviceName = "未命名设备";
}
console.log(deviceName); // "未命名设备"

1.3 多分支 if-else if-else

// IoT场景:温度分级报警系统
let temperature: number = 75.0;

if (temperature >= 90.0) {
  console.log("严重过热,立即关闭设备!");
} else if (temperature >= 80.0) {
  console.log("温度过高,启动紧急散热");
} else if (temperature >= 60.0) {
  console.log("温度偏高,注意监控");
} else if (temperature >= 20.0) {
  console.log("温度正常");
} else {
  console.log("温度过低,检查环境");
}

执行逻辑

  • 从上到下依次判断条件
  • 遇到第一个为 true 的条件,执行对应代码块,然后跳出整个 if 结构
  • 如果所有条件都不满足,执行 else 块(如果有)

1.4 嵌套 if 与优化技巧

嵌套 if 示例

// IoT场景:设备综合状态判断
let isOnline: boolean = true;
let hasError: boolean = false;
let voltage: number = 220.0;

if (isOnline) {
  if (hasError) {
    console.log("设备在线但有故障");
  } else {
    if (voltage >= 200.0 && voltage <= 240.0) {
      console.log("设备运行正常");
    } else {
      console.log("设备在线但电压异常");
    }
  }
} else {
  console.log("设备离线");
}

优化技巧1:提前返回(减少嵌套)

// 优化后:使用提前返回
function checkDeviceStatus(
  isOnline: boolean,
  hasError: boolean,
  voltage: number
): string {
  if (!isOnline) {
    return "设备离线";
  }
  
  if (hasError) {
    return "设备在线但有故障";
  }
  
  if (voltage < 200.0 || voltage > 240.0) {
    return "设备在线但电压异常";
  }
  
  return "设备运行正常";
}

console.log(checkDeviceStatus(true, false, 220.0)); // "设备运行正常"

优化技巧2:反转条件

// 反转条件减少嵌套
function processSensorData(data: number[] | null): void {
  // ❌ 不好的写法:嵌套过深
  // if (data !== null) {
  //   if (data.length > 0) {
  //     // 处理数据
   //   }
  // }
  
  // ✅ 优化:先处理边界情况
  if (data === null) {
    console.log("数据为空");
    return;
  }
  
  if (data.length === 0) {
    console.log("数据长度为0");
    return;
  }
  
  // 主逻辑在这里,无嵌套
  console.log(`处理${data.length}条数据`);
}

1.5 条件表达式的隐式转换规则(truthy/falsy)

ArkTS 支持数字、字符串等类型的隐式转换为 boolean!

类型 falsy 值(转换为 false) truthy 值(转换为 true)
number 0 非零数字
string ""(空字符串) 非空字符串
boolean false true
null null
undefined undefined
// number 的隐式转换
let count: number = 5;
if (count) {
  console.log("数量非零"); // 会执行
}

let zero: number = 0;
if (zero) {
  console.log("这不会执行"); // 0 是 falsy
} else {
  console.log("数量为零"); // 会执行
}

// string 的隐式转换
let deviceId: string = "SENSOR_001";
if (deviceId) {
  console.log("设备ID有效"); // 会执行
}

let emptyName: string = "";
if (emptyName) {
  console.log("这不会执行"); // 空字符串是 falsy
} else {
  console.log("名称为空"); // 会执行
}

// null/undefined 的隐式转换
let sensor: string | null = null;
if (sensor) {
  console.log("传感器已连接");
} else {
  console.log("传感器未连接"); // 会执行(null 是 falsy)
}

⚠️ 最佳实践:虽然支持隐式转换,但在复杂场景下建议显式比较,提高可读性:

// 简单场景:隐式转换简洁
if (count) { }

// 复杂场景:显式比较更清晰
if (temperature > 80.0 && isOnline && !hasError) { }

【第一部分小结】

语法 说明 示例
if 单分支 if (x > 0) { }
if-else 双分支 if (x > 0) { } else { }
if-else if-else 多分支 if (x > 0) { } else if (x < 0) { } else { }
提前返回 减少嵌套 if (!valid) return;
条件转换 支持隐式转换 if (count), if (name)

第二部分:switch 语句

2.1 基本语法

语法与C++相同:根据表达式的值跳转到匹配的 case。

// IoT场景:设备类型处理
let deviceType: string = "LED";
let brightness: number = 0;

switch (deviceType) {
  case "LED":
    console.log("LED灯设备");
    brightness = 100;
    break;
  case "RGB":
    console.log("RGB彩灯设备");
    brightness = 100;
    break;
  case "DIMM":
    console.log("可调光灯设备");
    brightness = 50;
    break;
  default:
    console.log("未知设备类型");
    brightness = 0;
}

console.log(`亮度设置为: ${brightness}`);

2.2 fall-through(穿透)

故意省略 break,让多个 case 共享代码:

// IoT场景:HTTP状态码分类
let statusCode: number = 404;

switch (statusCode) {
  case 200:
  case 201:
  case 204:
    console.log("请求成功");
    break;
  case 400:
  case 401:
  case 403:
  case 404:
    console.log("客户端错误");
    break;
  case 500:
  case 502:
  case 503:
    console.log("服务器错误");
    break;
  default:
    console.log("未知状态码");
}

2.3 enum + switch 结合(ArkTS 最佳实践)

结合 Day 01 学过的枚举,实现类型安全的状态机:

// IoT场景:设备状态枚举
enum DeviceState {
  OFFLINE = 0,
  CONNECTING = 1,
  ONLINE = 2,
  ERROR = 3
}

function handleDeviceState(state: DeviceState): string {
  switch (state) {
    case DeviceState.OFFLINE:
      return "设备离线,尝试重连";
    case DeviceState.CONNECTING:
      return "设备连接中,请稍候";
    case DeviceState.ONLINE:
      return "设备在线,正常工作";
    case DeviceState.ERROR:
      return "设备故障,需要维修";
    default:
      // ArkTS 编译器会检查是否覆盖所有枚举值
      // 这里的 default 用于防御性编程
      return `未知状态: ${state}`;
  }
}

let currentState: DeviceState = DeviceState.ONLINE;
console.log(handleDeviceState(currentState)); // "设备在线,正常工作"

字符串枚举同样适用

enum CommandType {
  POWER_ON = "POWER_ON",
  POWER_OFF = "POWER_OFF",
  SET_BRIGHTNESS = "SET_BRIGHTNESS",
  GET_STATUS = "GET_STATUS"
}

function executeCommand(cmd: CommandType, param?: string): string {
  switch (cmd) {
    case CommandType.POWER_ON:
      return "设备已开启";
    case CommandType.POWER_OFF:
      return "设备已关闭";
    case CommandType.SET_BRIGHTNESS:
      return `亮度设置为${param ?? "50"}%`;
    case CommandType.GET_STATUS:
      return "设备状态正常";
    default:
      return "未知命令";
  }
}

2.4 switch 的本质与类型支持

switch 的本质:case 匹配使用的是 === 严格相等比较。

switch (x) {
  case a:  // 相当于 if (x === a)
    break;
}

支持的类型

  • numberstring:可以直接使用
  • 联合类型 number | string:case 可以分别匹配不同类型的值
  • 引用类型(数组、对象):比较的是地址,不是内容(很少使用)

类型检查:case 值的类型必须与 switch 表达式类型有交集,否则编译错误。

let x: string = "A";
let y: number = 1;

switch (x) {
  case y:  // ❌ 编译错误:string 和 number 无交集
    break;
}

2.5 switch vs if 选择指南

场景 推荐 原因
单个变量与多个确定值比较 switch 结构清晰,编译器可优化
范围判断(如 > 80) if switch 无法处理范围
复杂条件组合 if switch 只能做相等比较
超过5个分支的相等比较 switch 可读性更好
枚举值处理 switch 类型安全,编译器检查完整性
// switch 适合:状态码、命令类型、枚举值
switch (command) {
  case CommandType.POWER_ON: 
    console.log("开启"); 
    break;
  case CommandType.POWER_OFF: 
    console.log("关闭"); 
    break;
}

// if 适合:范围判断、复杂条件
if (temperature > 80.0 && isOnline) { }
else if (voltage < 200.0 || voltage > 240.0) { }

【第二部分小结】

特性 语法 说明
基本语法 switch (expr) { case val: ... break; } 与C++相同
fall-through 省略 break 多个case共享代码
enum + switch case Enum.VALUE: ArkTS最佳实践
default default: 处理未匹配的情况

第三部分:条件表达式与模式

3.1 三元运算符在条件赋值中的应用

语法与C++相同condition ? valueIfTrue : valueIfFalse

// IoT场景:根据温度决定警告级别
let temperature: number = 92.5;
let warningLevel: string = temperature > 90.0 ? "高温警告" : "正常";
console.log(warningLevel); // "高温警告"

// 嵌套三元运算符(控制嵌套层数,保持可读性)
let alertLevel: string =
  temperature > 100.0 ? "紧急" :
  temperature > 90.0 ? "警告" :
  temperature > 80.0 ? "注意" : "正常";
console.log(alertLevel); // "警告"

⚠️ 建议:嵌套超过2层时,优先使用 if-else 以提高可读性。


3.2 短路求值(&& 和 ||)的条件执行

利用短路特性实现条件执行

// IoT场景:条件执行函数调用
let isReady: boolean = true;
let hasError: boolean = false;

// && 短路:左侧为 false,右侧不执行
isReady && console.log("设备就绪,开始处理"); // 会执行
hasError && console.log("记录错误日志");      // 不会执行

// 等同于
if (isReady) {
  console.log("设备就绪,开始处理");
}

// || 短路:左侧为 true,右侧不执行
let deviceName: string = "";
let displayName: string = deviceName || "未命名设备";
console.log(displayName); // "未命名设备"

// ⚠️ 注意:0 和 "" 也是 falsy,可能触发默认值
let packetRate: number = 0;
let rate: number = packetRate || 1000;
console.log(`${rate}`); // "1000"(0 被替换,可能不是预期行为!)

3.3 空值处理模式(?? 和 ?. 配合条件判断)

?? 空值合并运算符:仅对 nullundefined 生效。

// IoT场景:传感器读数可能为 null
let rawReading: number | null = null;

// ✅ ?? 只对 null/undefined 生效
let reading1: number = rawReading ?? -1;
console.log(`${reading1}`); // "-1"

// 当传感器读到 0(合法值)
let rawReading2: number | null = 0;
let reading2: number = rawReading2 ?? -1;
console.log(`${reading2}`); // "0"(正确保留0)

// ⚠️ 对比 ||:会把 0 也替换掉
let reading3: number = rawReading2 || -1;
console.log(`${reading3}`); // "-1"(错误!0 是合法值)

?. 可选链 + ?? 组合使用

// IoT场景:嵌套对象安全访问
interface SensorConfig {
  name: string;
  calibration: {
    offset: number;
    scale: number;
  } | null;
}

let config: SensorConfig | null = null;

// 安全获取校准参数,提供默认值
let offset: number = config?.calibration?.offset ?? 0.0;
let scale: number = config?.calibration?.scale ?? 1.0;
console.log(`offset=${offset}, scale=${scale}`); // "offset=0, scale=1"

【第三部分小结】

运算符/模式 说明 示例
?: 三元运算符 const x = a > b ? a : b;
&& 逻辑与,短路求值 isReady && start();
|| 逻辑或,短路求值 name || "default"
?? 空值合并(仅null/undefined) value ?? default
?. 可选链 obj?.prop?.method()

第四部分:类型守卫(ArkTS 重要模式)

4.1 typeof 类型守卫(联合类型分支处理)

函数参数联合类型时,使用 typeof 检查来区分类型:

// IoT场景:处理不同类型的传感器ID
function processSensorId(id: string | number): string {
  // typeof 类型守卫
  if (typeof id === "string") {
    // 在这个分支中,id 被收窄为 string 类型
    return `字符串ID: ${id.toUpperCase()}`;
  } else {
    // 在这个分支中,id 被收窄为 number 类型
    return `数字ID: ${id.toFixed(0)}`;
  }
}

console.log(processSensorId("sensor_001")); // "字符串ID: SENSOR_001"
console.log(processSensorId(1001));           // "数字ID: 1001"

⚠️ 重要限制:类型守卫主要用于函数参数。对于局部变量,如果实际类型已确定,else 分支可能被推断为 never

let id: string | number = "sensor_001";  // 实际就是 string

if (typeof id === "string") {
  // id 是 string
} else {
  // ❌ 这里 id 是 never,不是 number
  // 因为 ArkTS 知道 id 一定是 "sensor_001"(string)
}

typeof 返回值

值类型 typeof 返回
number "number"
string "string"
boolean "boolean"
undefined "undefined"
null "object" ⚠️
数组 "object"

4.2 instanceof 类型守卫(类继承分支处理)

检查对象是否是某个类的实例:

// IoT场景:不同类型的传感器处理
class Sensor {
  name: string = "Generic Sensor";
  
  read(): string {
    return "基础传感器读数";
  }
}

class TemperatureSensor extends Sensor {
  temperature: number = 25.0;
  
  read(): string {
    return `温度: ${this.temperature}°C`;
  }
}

class HumiditySensor extends Sensor {
  humidity: number = 60.0;
  
  read(): string {
    return `湿度: ${this.humidity}%`;
  }
}

function processSensor(sensor: Sensor): string {
  if (sensor instanceof TemperatureSensor) {
    // sensor 被收窄为 TemperatureSensor
    return `温度传感器 - ${sensor.read()}`;
  } else if (sensor instanceof HumiditySensor) {
    // sensor 被收窄为 HumiditySensor
    return `湿度传感器 - ${sensor.read()}`;
  } else {
    return `未知传感器 - ${sensor.read()}`;
  }
}

let tempSensor: TemperatureSensor = new TemperatureSensor();
console.log(processSensor(tempSensor)); // "温度传感器 - 温度: 25°C"

⚠️ 限制instanceof 左操作数必须是引用类型(对象、数组、类实例),基础类型(number/string/boolean)不能使用。


4.3 null/undefined 检查与类型收窄

显式检查 nullundefined,编译器会自动收窄类型:

// IoT场景:处理可能为空的传感器数据
function calculateAverage(readings: number[] | null): string {
  // null 检查实现类型收窄
  if (readings === null) {
    return "无数据";
  }
  
  // 在这个分支中,readings 被收窄为 number[]
  if (readings.length === 0) {
    return "数据为空数组";
  }
  
  let sum: number = 0;
  for (let i: number = 0; i < readings.length; i++) {
    sum += readings[i];
  }
  let avg: number = sum / readings.length;
  return `平均值: ${avg.toFixed(2)}`;
}

console.log(calculateAverage(null));           // "无数据"
console.log(calculateAverage([]));             // "数据为空数组"
console.log(calculateAverage([20, 25, 30]));   // "平均值: 25.00"

4.4 ArkTS 流敏感类型检查

ArkTS 编译器支持流敏感类型检查——在 if 分支内,经过类型检查后,编译器会自动收窄变量类型。

// IoT场景:联合类型的流敏感类型收窄
function processValue(value: string | number | null): string {
  // 首先检查 null
  if (value === null) {
    return "值为空";
  }
  
  // 此时 value 被收窄为 string | number
  if (typeof value === "string") {
    // 此时 value 被收窄为 string
    return `字符串长度: ${value.length}`;
  }
  
  // 此时 value 被收窄为 number
  return `数字平方: ${value * value}`;
}

console.log(processValue(null));       // "值为空"
console.log(processValue("hello"));    // "字符串长度: 5"
console.log(processValue(5));          // "数字平方: 25"

⚠️ 重要限制:跨分支类型比较

ArkTS 中不同类型的变量不能直接用 === 比较:

let a: number = 42;
let b: string = "42";

// ❌ 编译错误:number 和 string 无交集,不能用 === 比较
// console.log(a === b);

// ✅ 必须先转换为相同类型
console.log(`${a}` === b);  // "true"
console.log(a === Number(b)); // true

联合类型与具体类型的比较

function checkValue(value: string | number): void {
  // ✅ 合法:value 与 "42" 有交集(string 部分)
  if (value === "42") {
    console.log("字符串42");
  }
  
  // ✅ 合法:value 与 42 有交集(number 部分)
  if (value === 42) {
    console.log("数字42");
  }
}

【第四部分小结】

类型守卫 用途 示例
typeof 区分基础类型 typeof x === "string"
instanceof 区分类实例 x instanceof MyClass
=== null 检查空值 if (x === null)
=== undefined 检查未定义 if (x === undefined)
流敏感检查 自动类型收窄 编译器在分支内自动收窄

第五部分:实际应用案例

5.1 设备状态机(enum + switch)

// IoT场景:智能灯设备状态机
enum LightState {
  OFF = "OFF",
  ON = "ON",
  DIMMING = "DIMMING",
  ERROR = "ERROR"
}

interface LightDevice {
  id: string;
  state: LightState;
  brightness: number;
}

function handleLightState(device: LightDevice): string {
  switch (device.state) {
    case LightState.OFF:
      return `灯${device.id}已关闭`;
      
    case LightState.ON:
      return `灯${device.id}已开启,亮度${device.brightness}%`;
      
    case LightState.DIMMING:
      return `灯${device.id}调光中,目标亮度${device.brightness}%`;
      
    case LightState.ERROR:
      return `灯${device.id}故障,请检查`;
      
    default:
      // 防御性编程
      return `灯${device.id}未知状态`;
  }
}

// 使用示例
let livingRoomLight: LightDevice = {
  id: "LIVING_ROOM_01",
  state: LightState.ON,
  brightness: 80
};

console.log(handleLightState(livingRoomLight)); // "灯LIVING_ROOM_01已开启,亮度80%"

5.2 传感器数据校验(综合条件模式 + 类型守卫)

// IoT场景:传感器数据校验与处理
interface SensorReading {
  sensorId: string;
  value: number;
  unit: string;
  timestamp: number;
}

interface ValidationResult {
  valid: boolean;
  message: string;
  reading?: SensorReading;
}

function validateSensorData(
  data: unknown
): ValidationResult {
  // 首先检查是否为对象
  if (data === null || typeof data !== "object") {
    return { valid: false, message: "数据必须是对象" };
  }
  
  // 类型收窄为对象后检查属性
  let obj = data as Record<string, unknown>;
  
  // 检查必需字段
  if (typeof obj.sensorId !== "string" || obj.sensorId === "") {
    return { valid: false, message: "sensorId 必须是有效字符串" };
  }
  
  if (typeof obj.value !== "number" || isNaN(obj.value)) {
    return { valid: false, message: "value 必须是有效数字" };
  }
  
  if (typeof obj.unit !== "string") {
    return { valid: false, message: "unit 必须是字符串" };
  }
  
  if (typeof obj.timestamp !== "number") {
    return { valid: false, message: "timestamp 必须是数字" };
  }
  
  // 构建有效结果
  let reading: SensorReading = {
    sensorId: obj.sensorId,
    value: obj.value,
    unit: obj.unit,
    timestamp: obj.timestamp
  };
  
  return { valid: true, message: "数据有效", reading: reading };
}

// 使用示例
let rawData = {
  sensorId: "TEMP_001",
  value: 25.5,
  unit: "°C",
  timestamp: Date.now()
};

let result: ValidationResult = validateSensorData(rawData);
if (result.valid && result.reading !== undefined) {
  console.log(`传感器 ${result.reading.sensorId} 读数: ${result.reading.value}${result.reading.unit}`);
} else {
  console.log(`校验失败: ${result.message}`);
}

测试题

题1:判断输出

以下代码输出什么?

let a: number = 0;
let b: string = "";
let c: string = "hello";
let d: number = 5;

if (a) {
  console.log("A");
} else if (b) {
  console.log("B");
} else if (c) {
  console.log("C");
} else if (d) {
  console.log("D");
} else {
  console.log("E");
}

答案:输出 C。解析:a 是 0(falsy),b 是空字符串(falsy),c 是非空字符串(truthy),所以执行 console.log("C")


题2:判断输出

以下代码输出什么?

let value: string | number = "test";
let result: string;

if (typeof value === "string") {
  result = `str:${value}`;
} else {
  result = `num:${value * 2}`;
}

console.log(result);

答案:输出 str:test

解析:value 实际赋值为 "test"(string),所以进入 typeof value === "string" 分支。注意:这个例子中类型守卫在局部变量上的行为和函数参数不同,else 分支实际上不可达。


题3:找错

以下代码有哪些 ArkTS 编译错误?请指出并修正。

// 代码A
let x: number = 10;
let y: string = "10";
console.log(x === y);

// 代码B
let count: number = 5;
console.log(count);

答案与修正

// 代码A:number 和 string 不能直接用 === 比较
// ❌ console.log(x === y);
// ✅ 修正:转换为相同类型
console.log(`${x}` === y);  // "true"

// 代码B:console.log 只接受 string 参数
// ❌ console.log(count);
// ✅ 修正:转换为 string
console.log(`${count}`);

题4:代码分析

以下代码有什么问题?如何修正?

let input: string | number = 100;
let result: string;

if (input) {
  result = input.toUpperCase();
}

答案与修正

// ❌ 错误:在 input 为 truthy 的分支中,input 仍是 string | number 联合类型
// number 类型没有 toUpperCase 方法

// ✅ 修正:使用 typeof 检查具体类型
let input: string | number = 100;
let result: string;

if (typeof input === "string") {
  result = input.toUpperCase();
} else {
  result = `${input}`;
}

注意:这个修正仅适用于演示。实际中,局部变量应该直接声明为具体类型 let input: number = 100;,不需要类型守卫。


题5:编程题

使用 switch 语句处理设备命令:

要求

  • 声明变量 command: string = "POWER_ON"
  • 使用 switch 语句处理以下命令:
    • "POWER_ON":输出 "设备已开启"
    • "POWER_OFF":输出 "设备已关闭"
    • "STATUS":输出 "设备状态正常"
    • 其他:输出 "未知命令"

参考答案

let command: string = "POWER_ON";
let result: string;

switch (command) {
  case "POWER_ON":
    result = "设备已开启";
    break;
  case "POWER_OFF":
    result = "设备已关闭";
    break;
  case "STATUS":
    result = "设备状态正常";
    break;
  default:
    result = "未知命令";
    break;
}

console.log(result);

题6:编程题

使用三元运算符和空值合并:

要求

  • 声明变量 value: number | null = null
  • 使用 ?? 运算符提供默认值 -1
  • 使用三元运算符判断:如果值大于 0 输出 "正数",否则输出 "非正数"

参考答案

let value: number | null = null;

// 使用 ?? 提供默认值
let actualValue: number = value ?? -1;

// 使用三元运算符判断
let result: string = actualValue > 0 ? "正数" : "非正数";

console.log(result);  // "非正数"(因为 value 是 null,?? 返回 -1)

扩展练习:尝试将 value 分别设为 null05,观察输出变化。


条件控制速查表

语法/概念 代码示例 说明
if 语句 if (x > 0) { } 条件为 truthy 时执行
if-else if (x) { } else { } 二选一执行
if-else if if (a) { } else if (b) { } 多分支选择
switch switch (x) { case v: ... } 多值匹配
fall-through case A: case B: ... break; 多个 case 共享代码
三元运算符 cond ? a : b 简单条件赋值
短路 && ready && start() 条件执行
短路 || name || "default" 默认值(注意 falsy)
空值合并 ?? value ?? default 仅对 null/undefined 生效
可选链 ?. obj?.prop?.method() 安全访问嵌套属性
typeof 守卫 typeof x === "string" 区分基础类型
instanceof x instanceof Class 区分类实例
null 检查 if (x !== null) 类型收窄为具体类型

truthy/falsy 速查

布尔结果
0 falsy
非零数字 truthy
"" falsy
非空字符串 truthy
null falsy
undefined falsy
true truthy
false falsy

posted @ 2026-04-07 10:59  thammer  阅读(1)  评论(0)    收藏  举报