散修带你入门鸿蒙应用开发基础第四节:核心内置对象与集合
ArkTS基础第四节:核心内置对象与集合
炼气四重天
【学习目标】
- 理解数组、集合(Map/Set)、元组、Record的核心概念,明确其适用场景
- 掌握
Number、String、Array的常用属性与方法,规避数组越界等常见错误 - 理解“原始类型”与“对象类型”的关系(如
number与Number) - 掌握元组(Tuple)的定义与使用,明确其与普通数组的差异
- 掌握
Map、Set集合及Record类型工具的使用场景与方法,能安全处理Map key不存在、Set无索引等场景
【学习重点】
- 核心概念:数组(动态有序列表)、元组(固定结构数组)、Map(键值对集合)、Set(唯一元素集合)、Record(强类型静态结构)
- 核心内置对象:
String字符串处理、Array数组操作(含越界规避) - 特殊数组:元组(Tuple)的固定长度/类型约束与使用场景
- 数值工具:
Number类型转换与格式化 - 集合类型:
Map(键值对集合,含key不存在处理)、Set(唯一元素集合)的使用 - 类型工具:
Record(固定结构对象类型约束)的使用 - 实战技巧:结合内置对象简化重复逻辑(数组去重、数据筛选等)、各类容器的避坑方法
【代码存放路径】
本节项目工程名称:BuiltInObjectsDemo,所有示例代码均存放于项目 ets/utils/BuiltInObjectsTest.ets 文件中,可直接复制到鸿蒙项目中运行调试。此前课程已详细讲解过鸿蒙工程创建、工具类文件新建的完整流程,且大家已掌握相关操作,因此本节课不再重复提示工程与文件的创建步骤。
数据容器类型介绍
数组/集合/Record是日常开发中最常用的“数据容器”,核心差异在于“数据组织方式”和“访问规则”:数组按索引有序存储,Map按键值映射存储,Set按唯一元素存储,元组是数组的“强约束版本”,Record是强类型静态结构,掌握这些差异能大幅减少代码BUG。
一、原始类型与内置对象的关系
原始类型(number、string、boolean 等)是数据的基本形态,而 Number、String、Array 等是对应的内置对象,提供丰富工具方法操作原始类型数据。
ArkTS 会自动完成“原始类型→内置对象”的隐式转换,因此原始类型可直接调用对应内置对象的方法,执行后自动还原为原始类型。
// 原始类型(存储具体值)
let num: number = 123;
let str: string = "hello";
// 直接调用内置对象的方法(ArkTS自动转换)
console.log(`num保留2位小数:${num.toFixed(2)}`); // 输出:num保留2位小数:123.00
console.log(`str长度:${str.length}`); // 输出:str长度:5
二、常用内置对象及方法
2.1 Number:数值处理工具
Number 对象用于处理数值类型数据,核心用法如下:
类型转换: 将字符串、布尔值等类型转换为数值,无效转换返回 NaN(非数字)。
// 字符串转数值(有效)
console.log(`字符串转数值:${Number("123")}`); // 输出:123
// 字符串转数值(无效)
console.log(`无效转换:${Number("123abc")}`); // 输出:NaN
// 布尔值转数值
console.log(`布尔值转数值:${Number(true)}`); // 输出:1
console.log(`布尔值转数值:${Number(false)}`); // 输出:0
保留小数: toFixed(n)保留指定位数的小数,返回字符串类型,注意浮点数精度偏差问题。
const pi = 3.1415926;
// 保留2位小数
console.log(`保留2位小数:${pi.toFixed(2)}`); // 输出:3.14
// 保留0位小数(取整)
console.log(`保留0位小数:${pi.toFixed(0)}`); // 输出:3
// 注意:浮点数精度偏差
const num1 = 0.15;
console.log(`0.15保留1位小数:${num1.toFixed(1)}`); // 输出:0.1(非预期的0.2)
判断整数: Number.isInteger(value)静态方法,仅判断原始数值是否为整数,非数值类型直接返回 false。
// 整数判断
console.log(`5是整数:${Number.isInteger(5)}`); // 输出:true
console.log(`5.5是整数:${Number.isInteger(5.5)}`); // 输出:false
// 非数值类型判断
console.log(`字符串'5'是整数:${Number.isInteger("5")}`); // 输出:false
判断NaN: Number.isNaN(value)
静态方法,仅对原始类型 number 的 NaN 生效,比全局 isNaN 更精准。
console.log(`NaN判断:${Number.isNaN(NaN)}`); // 输出:true
console.log(`字符串'NaN'判断:${Number.isNaN("NaN")}`); // 输出:false
console.log(`123是否为NaN:${Number.isNaN(123)}`); // 输出:false
2.2 String:字符串处理工具
String 对象是处理字符串的核心工具,核心用法如下:
获取长度: length获取字符串的字符个数,包含空格、符号等所有字符。
const str1 = "hello";
console.log(`str1长度:${str1.length}`); // 输出:5
// 包含空格的字符串
const strWithSpace = " hi! ";
console.log(`带空格字符串长度:${strWithSpace.length}`); // 输出:6
查找子串: indexOf(str) 查找子串首次出现的索引(从0开始),找不到返回 -1,区分大小写。
const str2 = "Hello ArkTS";
// 区分大小写查找
console.log(`'Ark'索引:${str2.indexOf("Ark")}`); // 输出:6
console.log(`'ark'索引:${str2.indexOf("ark")}`); // 输出:-1
// 不区分大小写查找技巧
const targetSubStr = "ark";
const ignoreCaseIndex = str2.toLowerCase().indexOf(targetSubStr.toLowerCase());
console.log(`不区分大小写查找'ark':${ignoreCaseIndex}`); // 输出:6
截取子串: slice(start, end)左闭右开截取子串(包含start,不包含end),支持负数索引(从末尾倒数)。
const str3 = "hello";
// 截取索引1到3的字符(不包含3)
console.log(`截取1-3:${str3.slice(1, 3)}`); // 输出:el
// 负数索引:倒数3位
console.log(`倒数3位:${str3.slice(-3)}`); // 输出:llo
// 从索引2开始截取到末尾
console.log(`从索引2开始:${str3.slice(2)}`); // 输出:llo
拆分数组: split(sep)按指定分隔符将字符串拆分为数组,支持限制拆分次数。
const str4 = "a,b,c,d";
// 全部分拆
console.log(`全部分拆:${str4.split(",")}`); // 输出:a,b,c,d
// 限制拆分次数(只拆前2个)
console.log(`拆分2次:${str4.split(",", 2)}`); // 输出:a,b
// 按空字符拆分(单个字符)
console.log(`单个字符拆分:${"abc".split("")}`); // 输出:a,b,c
去除空格: trim()去除字符串首尾的空格,不影响中间的空格。
const str5 = " hello world ";
console.log(`去除空格后:${str5.trim()}`); // 输出:hello world
大小写转换: toUpperCase/toLowerCase
const str6 = "Hello";
// 转大写
console.log(`转大写:${str6.toUpperCase()}`); // 输出:HELLO
// 转小写
console.log(`转小写:${str6.toLowerCase()}`); // 输出:hello
判断包含: includes(substr)判断字符串是否包含指定子串,返回布尔值,区分大小写。
const str7 = "HarmonyOS 开发";
console.log(`是否包含"开发":${str7.includes("开发")}`); // 输出:true
console.log(`是否包含"鸿蒙":${str7.includes("鸿蒙")}`); // 输出:false
替换子串: replace(oldStr, newStr)替换指定子串,支持正则表达式实现全局替换。
const str8 = "ArkTS 很强大,ArkTS 很易用";
// 替换首次出现的子串
console.log(`替换首次:${str8.replace("ArkTS", "ArkTS 4.0")}`); // 输出:ArkTS 4.0 很强大,ArkTS 很易用
// 全局替换(正则/g)
console.log(`全局替换:${str8.replace(/ArkTS/g, "ArkTS 4.0")}`); // 输出:ArkTS 4.0 很强大,ArkTS 4.0 很易用
2.3 Array:数组操作工具
核心用法
获取长度: length获取数组元素个数,推荐使用 类型[] 写法声明数组。
// 推荐写法:number类型数组
let arr1: number[] = [1, 2, 3];
// let arr2:Array<number> = new Array(1,2,3) // 等价
console.log(`arr1长度:${arr1.length}`); // 输出:3
末尾添加 push(item)
向数组末尾添加元素,直接修改原数组,返回新数组长度。
const arr2 = [1];
// 添加元素2、3
const newLen = arr2.push(2, 3);
console.log(`添加后数组:${arr2}`); // 输出:1,2,3
console.log(`新数组长度:${newLen}`); // 输出:3
末尾删除 pop()
删除数组最后一个元素,直接修改原数组,返回被删除的元素。
const arr3 = [1, 2, 3];
// 删除最后一个元素
const deletedItem = arr3.pop();
console.log(`删除后数组:${arr3}`); // 输出:1,2
console.log(`被删除元素:${deletedItem}`); // 输出:3
截取子数组 slice(start, end)
左闭右开截取子数组,不修改原数组,返回新数组。
const arr4 = [1, 2, 3, 4];
// 截取索引0到2的元素(不包含2)
const sliceArr = arr4.slice(0, 2);
console.log(`截取的子数组:${sliceArr}`); // 输出:1,2
console.log(`原数组(无变化):${arr4}`); // 输出:1,2,3,4
增删元素 splice(start, len)
删除/添加数组元素,直接修改原数组,返回被删除的元素数组。
const arr5 = [1, 2, 3, 4];
// 从索引1开始,删除1个元素
const deletedArr = arr5.splice(1, 1);
console.log(`被删除元素:${deletedArr}`); // 输出:2
console.log(`修改后数组:${arr5}`); // 输出:1,3,4
slice vs splice 核心区别
const arrCompare = [1,2,3,4];
// slice:不修改原数组
const sliceArr2 = arrCompare.slice(0,2);
console.log(`slice截取结果:${sliceArr2}`); // 输出:1,2
console.log(`slice后原数组:${arrCompare}`); // 输出:1,2,3,4(无变化)
// splice:修改原数组
const spliceArr = arrCompare.splice(0,2);
console.log(`splice删除结果:${spliceArr}`); // 输出:1,2
console.log(`splice后原数组:${arrCompare}`); // 输出:3,4(已修改)
拼接字符串 join(sep)
用指定分隔符将数组元素拼接为字符串。
const arr6 = [1, 2, 3];
// 用连字符分隔
console.log(`连字符分隔:${arr6.join("-")}`); // 输出:1-2-3
// 无分隔符拼接
console.log(`无分隔符:${arr6.join("")}`); // 输出:123
// 用中文逗号分隔
console.log(`中文逗号分隔:${arr6.join("、")}`); // 输出:1、2、3
判断包含 includes(item)
判断数组是否包含指定元素,返回布尔值。
const arr8 = [1,2,3,4];
console.log(`是否包含3:${arr8.includes(3)}`); // 输出:true
console.log(`是否包含5:${arr8.includes(5)}`); // 输出:false
拼接数组 concat(arr1, arr2...)
拼接多个数组,不修改原数组,返回新数组。
const arr9 = [1,2];
const arr10 = [3,4];
// 拼接arr9、arr10和新数组[5,6]
const mergedArr = arr9.concat(arr10, [5,6]);
console.log(`拼接后数组:${mergedArr}`); // 输出:1,2,3,4,5,6
console.log(`原数组(无变化):${arr9}`); // 输出:1,2
循环遍历(for循环)
用普通for循环遍历数组所有元素。
const numbers: number[] = [1, 2, 3, 4, 5];
console.log("数组遍历结果:");
for (let i = 0; i < numbers.length; i++) {
console.log(`第${i+1}个元素:${numbers[i]}`);
// 依次输出:1、2、3、4、5
}
实战场景:处理用户列表数据
// 声明用户类型(简化版,仅基础属性)
interface User = {
name: string;
age: number;
};
// 用户列表数据
const users: User[] = [
{ name: "张三", age: 18 },
{ name: "李四", age: 22 },
{ name: "王五", age: 17 }
];
// 筛选成年用户(年龄≥18)
const adults: User[] = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
adults.push(users[i]);
}
}
console.log(`成年用户:${JSON.stringify(adults)}`);
// 输出:[{"name":"张三","age":18},{"name":"李四","age":22}]
// 提取成年用户姓名并拼接
const adultNames: string[] = [];
for (let i = 0; i < adults.length; i++) {
adultNames.push(adults[i].name);
}
console.log(`成年用户姓名:${adultNames.join("、")}`);
// 输出:张三、李四
关键避坑:数组越界访问
什么是数组越界?
访问数组不存在的索引(索引≥数组长度 或 负数索引未处理),称为“越界访问”。
- 普通数组:越界访问编译期不报错,运行时返回
undefined,易导致后续逻辑异常; - 元组:越界访问编译期直接报错,从根源规避风险(核心优势)。
越界场景与风险
const arr: number[] = [1, 2, 3]; // 长度3,有效索引:0、1、2
// 场景1:索引≥长度(越界)
console.log(arr[3]); // 输出:undefined(编译不报错,运行时异常)
// 场景2:负数索引未处理(越界)
console.log(arr[-1]); // 输出:undefined(编译不报错)
// 风险:越界返回undefined,参与运算会导致NaN
const sum = arr[0] + arr[3]; // 1 + undefined = NaN
console.log(sum); // 输出:NaN
规避方法
- 访问前校验索引合法性;
- 遍历数组优先用
for循环(自动限制索引范围); - 结构化固定数据优先用元组(编译期防越界)。
const arr: number[] = [1, 2, 3];
const targetIndex = 3;
// 方法1:索引合法性校验
if (targetIndex >= 0 && targetIndex < arr.length) {
console.log(`合法访问:${arr[targetIndex]}`);
} else {
console.log(`索引${targetIndex}越界,数组长度:${arr.length}`); // 输出:索引3越界,数组长度:3
}
// 方法2:优先用for循环遍历(无越界风险)
for (let i = 0; i < arr.length; i++) {
console.log(`索引${i}:${arr[i]}`); // 仅访问0/1/2,无越界
}
// 方法3:结构化数据用元组(编译期防越界)
const tuple: [number, number, number] = [1, 2, 3];
// console.log(tuple[3]); // 编译直接报错:索引3超出元组长度3
2.4 元组(Tuple):固定长度/类型的数组
核心语法
// 定义:[类型1, 类型2, ...](指定每个位置的类型)
let 元组名: [类型1, 类型2, 类型3] = [值1, 值2, 值3];
基础示例
// 定义元组:第1位string(姓名)、第2位number(年龄)、第3位boolean(是否成年)
let userInfo: [string, number, boolean] = ["张三", 18, true];
// 访问元组元素(通过索引,与数组一致)
console.log(`姓名:${userInfo[0]}`); // 输出:张三
console.log(`年龄:${userInfo[1]}`); // 输出:18
console.log(`是否成年:${userInfo[2]}`); // 输出:true
类型/长度强约束
元组的核心特性是“编译期校验”,长度/类型不匹配直接报错:
let point: [number, number] = [100, 200]; // 正确:2个数字(坐标x,y)
// 错误1:长度不匹配(元组要求2个元素,传入3个)
// point = [100, 200, 300];
// 错误2:类型不匹配(第2位要求number,传入string)
// point = [100, "200"];
// 错误3:越界访问(元组仅2个元素,访问索引2)
// console.log(point[2]);
常用场景:存储结构化基础数据(坐标、日期等)
// 场景1:坐标数据(x: number, y: number)
let position: [number, number] = [50, 80];
console.log(`坐标:x=${position[0]}, y=${position[1]}`); // 输出:x=50, y=80
// 场景2:日期数据(年: number, 月: number, 日: number)
let date: [number, number, number] = [2025, 12, 4];
console.log(`日期:${date[0]}年${date[1]}月${date[2]}日`); // 输出:2025年12月4日
// 场景3:商品基础信息(名称: string, 价格: number, 库存: number)
let product: [string, number, number] = ["鸿蒙耳机", 199, 50];
console.log(`商品名称:${product[0]},价格:${product[1]}元,库存:${product[2]}件`);
// 输出:商品名称:鸿蒙耳机,价格:199元,库存:50件
元组 vs 普通数组
| 对比维度 | 元组(Tuple) | 普通数组(Array) |
|---|---|---|
| 长度约束 | 固定长度(编译期校验) | 动态长度(可随意增删) |
| 类型约束 | 每个位置类型固定(如[string, number]) | 所有元素类型统一(如number[]) |
| 越界访问 | 编译期报错 | 运行时返回undefined(无编译报错) |
| 适用场景 | 结构化固定数据(坐标、日期等) | 动态列表数据(用户列表、成绩列表等) |
| 灵活性 | 低(强约束) | 高(弱约束) |
三、高级集合类型与类型工具
3.1 Map:键值对映射集合
创建与存储
// 创建Map(指定键为string类型,值为number类型)
const userMap: Map<string, number> = new Map<string, number>();
// 存储键值对(支持链式调用)
userMap.set("age", 18);
userMap.set("score", 95);
userMap.set("grade", 3);
取值与判断
// 获取指定键的值
const scoreValue = userMap.get("score");
console.log(`score的值:${scoreValue}`); // 输出:95
// 判断是否包含指定键
const hasNameKey = userMap.has("name");
console.log(`是否包含name键:${hasNameKey}`); // 输出:false
// 获取Map大小(元素个数)
console.log(`Map大小:${userMap.size}`); // 输出:3
删除与清空
// 删除指定键
const deleteResult = userMap.delete("age");
console.log(`删除age是否成功:${deleteResult}`); // 输出:true
console.log(`删除后大小:${userMap.size}`); // 输出:2
// 清空所有键值对
userMap.clear();
console.log(`清空后大小:${userMap.size}`); // 输出:0
遍历Map
// 创建带初始值的Map
const studentMap: Map<string, number> = new Map([
["张三", 85],
["李四", 92],
["王五", 78]
]);
// 遍历Map
console.log("学生成绩:");
for (const entry of studentMap) {
// entry[0] = 键,entry[1] = 值
console.log(`${entry[0]}:${entry[1]}分`);
}
关键避坑:Map key不存在的处理
key不存在的场景与返回值
调用 map.get(key) 时,若key不存在,返回 undefined,直接使用会导致逻辑异常:
const userMap: Map<string, number> = new Map();
userMap.set("张三", 85);
// 场景1:访问不存在的key
const score = userMap.get("李四");
console.log(score); // 输出:undefined
// 风险:undefined参与运算,导致NaN
const total = score + 10; // undefined + 10 = NaN
console.log(total); // 输出:NaN
安全取值方法
- 取值前用
map.has(key)校验; - 取值后设置默认值(避免undefined)。
const userMap: Map<string, number> = new Map([["张三", 85]]);
const targetUser = "李四";
// 方法1:先校验后取值
if (userMap.has(targetUser)) {
console.log(`${targetUser}的成绩:${userMap.get(targetUser)}`);
} else {
console.log(`${targetUser}不存在,默认成绩:0`); // 输出:李四不存在,默认成绩:0
}
// 方法2:取值时设置默认值(推荐)
const score = userMap.get(targetUser) ?? 0; // 不存在则取0
console.log(`${targetUser}的成绩:${score}`); // 输出:李四的成绩:0
Map 核心特性总结
- 键支持任意类型(数字、字符串、Symbol等),且键唯一;
- 遍历顺序与插入顺序严格一致;
- 可通过
size属性直接获取元素个数; - 频繁增删键值对时性能优于其他静态结构;
- 泛型约束键值类型,编译期可校验类型合法性。
3.2 Set:唯一元素集合
创建与添加
// 创建Set(指定存储string类型)
const tagSet: Set<string> = new Set<string>();
// 添加元素(重复元素自动忽略)
tagSet.add("前端开发");
tagSet.add("ArkTS");
tagSet.add("鸿蒙");
tagSet.add("ArkTS"); // 重复,不会存储
console.log(`Set大小:${tagSet.size}`); // 输出:3
判断与删除
// 判断是否包含指定元素
const hasHarmony = tagSet.has("鸿蒙");
console.log(`是否包含鸿蒙:${hasHarmony}`); // 输出:true
// 删除指定元素
const deleteResultSet = tagSet.delete("ArkTS");
console.log(`删除ArkTS是否成功:${deleteResultSet}`); // 输出:true
// 清空Set
tagSet.clear();
console.log(`清空后大小:${tagSet.size}`); // 输出:0
遍历Set
// 创建带初始值的Set(自动去重)
const fruitSet: Set<string> = new Set(["苹果", "香蕉", "橙子", "苹果"]);
console.log("水果列表:");
for (const fruit of fruitSet) {
console.log(fruit); // 输出:苹果、香蕉、橙子(无重复)
}
关键特性:无索引,不可通过下标访问
Set 没有索引,不能像数组一样用 set[0] 访问元素,这是新手最易踩的坑:
const fruitSet: Set<string> = new Set(["苹果", "香蕉"]);
// 错误:Set无索引,访问返回undefined
console.log(fruitSet[0]); // 输出:undefined
// 正确:遍历访问 或 转换为数组后访问
// 方式1:遍历
for (const fruit of fruitSet) {
console.log(fruit); // 输出:苹果、香蕉
}
// 方式2:转数组后索引访问
const fruitArr = Array.from(fruitSet);
console.log(fruitArr[0]); // 输出:苹果
Set与数组转换
推荐用 Array.from() 实现Set和数组的互转(ArkTS 不支持扩展运算符解构)。
// Set转数组
const fruitArr = Array.from(fruitSet);
console.log(`Set转数组:${fruitArr}`); // 输出:苹果,香蕉,橙子
// 数组去重(核心用法)
const duplicateArr = [1, 2, 2, 3, 3, 3];
const uniqueArr = Array.from(new Set(duplicateArr));
console.log(`原数组:${duplicateArr}`); // 输出:1,2,2,3,3,3
console.log(`去重后:${uniqueArr}`); // 输出:1,2,3
Set与数组的区别
| 对比维度 | Set(唯一元素集合) |
Array(有序数组) |
|---|---|---|
| 元素重复性 | 不允许重复(自动去重) | 允许重复元素 |
| 元素查找效率 | 高(哈希表实现,O(1)) | 低(遍历查找,O(n)) |
| 索引访问 | 无索引,不能通过下标访问(如set[0]) | 支持索引访问(如arr[0]) |
| 核心用途 | 存储唯一元素、快速去重、判断存在性 | 存储有序数据、需要索引访问、允许重复 |
3.3 Record:强类型固定结构对象
基础语法
// 语法:Record<键类型联合, 值类型>
type 类型名 = Record<键1 | 键2 | 键3, 值类型>;
- 键类型联合:指定对象允许的所有键(仅支持
string/number/symbol); - 值类型:所有键对应值的统一类型(或联合类型)。
基础用法(用户信息约束)
// 约束对象只能有name和age两个键,值为string或number
type UserInfo = Record<'name' | 'age', string | number>;
// 符合约束的对象
const userParam: UserInfo = {
"name": "张三",
"age": 18
};
console.log(`用户信息:${JSON.stringify(userParam)}`); // 输出:{"name":"张三","age":18}
场景示例(商品配置约束)
// 约束商品参数结构
type GoodsConfig = Record<'id' | 'name' | 'price', number | string>;
// 符合约束的商品参数
const goods: GoodsConfig = {
"id": 1001,
"name": "鸿蒙手机壳",
"price": 29.9
};
console.log(`商品参数:${JSON.stringify(goods)}`);
错误示例(编译期报错)
// 错误1:包含约束外的键(gender)
// const badUser: UserInfo = {
// 'name': "李四",
// 'age': 20,
// 'gender': "男" // 编译报错:对象文字可以只指定已知属性
// };
// 错误2:值类型不匹配
// const badGoods: GoodsConfig = {
// 'id': 1001,
// 'name': "鸿蒙手机壳",
// 'price': true // 编译报错:类型boolean不能赋值给类型string | number
// };
Record 核心特性总结
- 仅作用于编译期,无运行时实例和额外开销;
- 严格约束对象的键范围和值类型,规避非法键/值;
- 适用于配置项、接口参数等固定结构的静态数据;
- 泛型加持,类型校验能力强,可大幅减少运行时错误。
四、核心数据容器总结
4.1 核心容器对比总表
| 数据容器 | 核心定位 | 核心特性 | 适用场景 |
|---|---|---|---|
| 普通数组(Array) | 有序动态列表 | 动态长度、索引访问、允许重复、运行时越界 | 成绩列表、用户列表等动态有序数据 |
| 元组(Tuple) | 强约束结构化数组 | 固定长度、类型强约束、编译期防越界 | 坐标、日期、商品基础信息等固定格式数据 |
| Map | 动态键值对集合 | 任意类型键、键唯一、插入顺序遍历、size属性 | 缓存、动态配置、数字ID映射等动态键值对 |
| Set | 唯一元素集合 | 元素唯一、无索引、查找效率高、自动去重 | 标签列表、ID去重、元素存在性判断 |
| Record | 强类型静态结构约束 | 仅编译期校验、无运行时实例、固定键范围 | 接口参数、固定配置项等需强校验的静态数据 |
4.2 选型核心原则
- 有序可重复列表 → 普通数组(Array);
- 固定格式结构化数据 → 元组(Tuple);
- 动态键值对(非字符串键/频繁增删) → Map;
- 去重/元素存在性判断 → Set;
- 固定结构静态数据(需强校验) → Record。
五、原始类型 vs 内置对象类型
| 类型类别 | 示例 | 特点 | 使用建议 |
|---|---|---|---|
| 原始类型 | number/string |
存储具体值,小写开头,轻量高效 | 日常开发优先使用 |
| 内置对象类型 | Number/String |
工具对象,大写开头,提供丰富方法 | 仅需调用方法时自动转换使用 |
| 特殊数组类型 | 元组(Tuple) | 固定长度/类型,强约束 | 结构化固定数据场景使用 |
| 类型工具 | Record |
仅编译期约束,无运行时实例 | 约束静态对象结构时使用 |
// 原始类型(推荐)
let num: number = 123;
let str: string = "hello";
// 内置对象类型(不推荐手动创建)
// let numObj: Number = new Number(123);
// let strObj: String = new String("hello");
// 元组类型(结构化数据)
let userTuple: [string, number] = ["张三", 18];
// 类型判断
console.log(`num是number类型:${typeof num === 'number'}`); // 输出:true
console.log(`str是string类型:${typeof str === 'string'}`); // 输出:true
// 核心特性:原始类型可直接调用对象方法(ArkTS自动转换)
console.log(`num保留2位小数:${num.toFixed(2)}`); // 无需手动创建Number对象
六、核心避坑总结
6.1 数组避坑
- 越界访问:访问前校验索引
0 ≤ index < arr.length,遍历优先用for循环; - 类型错误:声明数组时指定明确类型(如
number[]),避免混合类型; slicevssplice:slice不修改原数组,splice直接修改原数组。
6.2 元组避坑
- 长度/类型约束:元组长度、类型固定,不可随意增删或修改类型;
- 越界防护:元组越界编译期报错,结构化固定数据优先用元组而非数组。
6.3 Map避坑
- key不存在:取值前用
has(key)校验,或用??设置默认值; - 键类型:Map键可以是任意类型,需注意键的唯一性。
6.4 Set避坑
- 无索引:Set不能通过下标访问,需遍历或转数组后访问;
- 元素唯一:Set自动去重,添加重复元素无效果(无报错)。
6.5 Record避坑
- Record仅编译期约束,无运行时能力,不可动态增删键;
- 键范围固定,超出声明范围的键会编译报错,需提前规划键列表。
【课堂小结】
- 核心定位:数组是有序索引列表,元组是强约束数组,Map是键值对集合,Set是唯一元素集合,Record是强类型静态结构约束工具;
- 原始类型(
number/string)可直接调用对应内置对象的方法,ArkTS 会自动完成类型转换; String高频方法:length/slice/split/trim/includes/replace;Array核心区别:slice(不修改原数组)、splice(修改原数组),需规避越界访问风险;- 元组是固定长度/类型的特殊数组,编译期防越界/类型错误,适用于坐标、日期等结构化固定数据;
Set + Array.from()是数组去重的极简方案,效率远高于手动遍历;Map适用于动态键值对(尤其是非字符串键),需处理key不存在的场景;Record是编译期类型工具,用于约束静态对象结构,无运行时开销;- 选型原则:根据“有序性、重复性、键类型、是否固定结构”选择对应容器,最大化利用编译期校验减少BUG。
七、【代码仓库】
第四节项目代码(BuiltInObjectsDemo)已同步:https://gitee.com/juhetianxia321/harmony-os-code-base.git
八、【课后练习】
-
操作题:处理字符串
const msg: string = " Hello World! ",实现:- ① 去除首尾空格;
- ② 转换为全小写;
- ③ 截取子串"world"(不区分大小写)。
-
操作题:处理数组
const scores: number[] = [85, 92, 78, 90, 88],实现:- ① 筛选出90分及以上的成绩;
- ② 计算所有成绩的平均分;
- ③ 将成绩转换为等级(90+为A,80-89为B,70-79为C)。
-
分析题:以下代码输出什么?为什么?
const arr: number[] = [1, 2, 3]; const newArr: number[] = arr.slice(0, 2); console.log(`原数组:${arr}`); console.log(`新数组:${newArr}`); -
元组应用题:定义一个元组
[string, number, string]存储“商品名称、价格、分类”,实现:- ① 创建元组并赋值(如["鸿蒙耳机", 199, "数码配件"]);
- ② 遍历元组打印每个元素;
- ③ 尝试修改元组第2位为字符串(如"199"),观察编译报错。
-
Map避坑题:用Map存储3个学生姓名+成绩(张三85、李四92、王五78),实现:
- ① 存储所有学生数据;
- ② 查询“李四”的成绩;
- ③ 计算3个学生的平均分;
- ④ 查询“赵六”的成绩,若不存在则返回默认值0。
-
Set应用题:用
Set处理数组const tags: string[] = ["前端", "ArkTS", "鸿蒙", "前端", "UI", "ArkTS"],实现:- ① 对数组去重;
- ② 统计去重后的元素个数;
- ③ 判断是否包含“UI”;
- ④ 将去重后的元素拼接为“前端、ArkTS、鸿蒙、UI”;
-
Record应用题:用
Record约束商品参数(id/name/price/category),创建符合约束的对象,并尝试添加非约束键(如"brand"),观察编译报错信息。 -
思考题:
- ①
Array的slice和splice有哪些核心区别?分别适用于什么场景? - ② 元组和普通数组的核心差异是什么?分别适用于哪些场景?
- ③ Map和Set的核心差异是什么?如何选择使用?
- ①
【下节预告】
下一节将进入函数体系的入门学习,解锁“炼气五重天”——函数的定义与使用(基础篇)。我们会掌握函数的核心封装逻辑,学会传统函数与箭头函数的定义、调用方法,熟练处理必需参数、可选参数、默认参数等不同输入场景,同时理解类型声明对函数安全性的关键作用,为后续复杂逻辑封装和高阶函数学习筑牢基础。
九、鸿蒙开发者学习与认证指引
(一)、官方学习班级报名(免费)
- 班级链接:HarmonyOS赋能资源丰富度建设(第四期)
- 学号填写规则:填写个人手机号码即可完成班级信息登记
(二)、HarmonyOS应用开发者认证考试(免费)
-
考试链接:HarmonyOS开发者能力认证入口
-
认证等级及适配人群
- 基础认证:适配软件工程师、移动应用开发人员,需掌握HarmonyOS基础概念、DevEco Studio基础使用、ArkTS及ArkUI基础开发等能力;
- 高级认证:适配项目经理、工程架构师,需掌握系统核心技术理念、应用架构设计、关键技术开发及应用上架运维等能力;
- 专家认证:适配研发经理、解决方案专家,需掌握分布式技术原理、端云一体化开发、跨端迁移及性能优化等高级能力。
-
认证权益:通过认证可获得电子版证书以及其他专属权益。
浙公网安备 33010602011771号