前言

在鸿蒙应用开发中,ArkTS作为主力语言,数组和Map是最高频使用的数据结构。熟练掌握它们的内置函数,可以大幅提升开发效率和代码质量。

我在做题时总会用到内置函数,对于初学者来说,花点时间总结这部分知识点,是很有效的,本文会从函数作用、参数、返回值、是否会改变原数据四个维度,对ArkTS中数组和Map的常用内置函数进行全面解析,并附上代码示例,希望能帮助大家快速上手。

一、数组(Array)内置函数详解

1. 增删操作

方法 参数 返回值 作用 是否改变原数组
push(value0, value1, ...) 要添加的一个或多个元素 新数组长度 向数组末尾添加元素 ✅ 是
pop() 被删除的元素 删除数组最后一个元素 ✅ 是
unshift(value0, value1, ...) 要添加的一个或多个元素 新数组长度 向数组开头添加元素 ✅ 是
shift() 被删除的元素 删除数组第一个元素 ✅ 是
splice(start, deleteCount, ...items) start: 起始索引;deleteCount: 删除个数;items: 要插入的元素 被删除元素组成的数组 增删改指定位置元素 ✅ 是
let arr: number[] = [10, 20, 30];

// push - 末尾添加
arr.push(40, 50);
console.log(arr); // [10, 20, 30, 40, 50]

// pop - 删除末尾
arr.pop();
console.log(arr); // [10, 20, 30, 40]

// unshift - 开头添加
arr.unshift(0, 5);
console.log(arr); // [0, 5, 10, 20, 30, 40]

// shift - 删除开头
arr.shift();
console.log(arr); // [5, 10, 20, 30, 40]

// splice - 指定位置操作
arr.splice(2, 1, 25, 26); // 在索引为2的位置,删除1个(这里指20),并添加25和26
console.log(arr); // [5, 10, 25, 26, 30, 40]

2. 遍历与迭代方法

方法 参数 返回值 作用 是否改变原数组
forEach(callbackFn) callbackFn: (item, index, array) => void undefined 对每个元素执行一次给定函数 ❌ 否
map(callbackFn) callbackFn: (item, index, array) => 新值 新数组 基于原数组映射出新数组 ❌ 否
filter(callbackFn) callbackFn: (item, index, array) => boolean 新数组 筛选出符合条件的元素 ❌ 否
reduce(callbackFn, initialValue) callbackFn: (prev, cur, index, array) => 累加值;initialValue: 初始值 累计计算结果 将数组汇总为单个值 ❌ 否
some(callbackFn) callbackFn: (item, index, array) => boolean boolean 是否有元素满足条件 ❌ 否
every(callbackFn) callbackFn: (item, index, array) => boolean boolean 是否所有元素满足条件 ❌ 否
let numbers: number[] = [1, 2, 3, 4, 5];

// forEach - 遍历
let sum = 0;
numbers.forEach(item => { sum += item; });
console.log(sum); // 15

// map - 映射
let doubled = numbers.map(x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// filter - 过滤
let filtered = numbers.filter(x => x > 3);
console.log(filtered); // [4, 5]

// reduce - 归并
let total = numbers.reduce((prev, cur) => prev + cur, 0);
console.log(total); // 15

// some - 是否存在满足条件的
let hasEven = numbers.some(x => x % 2 === 0);
console.log(hasEven); // true

// every - 是否全部满足
let allPositive = numbers.every(x => x > 0);
console.log(allPositive); // true

3. 查找方法

方法 参数 返回值 作用 是否改变原数组
indexOf(searchElement, fromIndex) searchElement: 要查找的元素;fromIndex: 起始索引 索引号或 -1 查找元素首次出现的位置 ❌ 否
lastIndexOf(searchElement, fromIndex) 同上 索引号或 -1 查找元素最后一次出现的位置 ❌ 否
find(callbackFn) callbackFn: (item, index, array) => boolean 符合条件的第一个元素值或 undefined 查找第一个满足条件的元素 ❌ 否
findIndex(callbackFn) callbackFn: (item, index, array) => boolean 符合条件的第一个元素的索引或 -1 查找第一个满足条件的索引 ❌ 否
includes(searchElement, fromIndex) searchElement: 要查找的元素;fromIndex: 起始索引 boolean 判断数组是否包含指定值 ❌ 否
let fruits: string[] = ['apple', 'banana', 'orange', 'banana'];

// indexOf
console.log(fruits.indexOf('banana'));     // 1
console.log(fruits.indexOf('banana', 2));  // 3

// lastIndexOf
console.log(fruits.lastIndexOf('banana')); // 3

// find
let found = fruits.find(fruit => fruit === 'orange');
console.log(found); // 'orange'

// findIndex
let index = fruits.findIndex(fruit => fruit === 'orange');
console.log(index); // 2

// includes
console.log(fruits.includes('grape')); // false

4. 数组转换与拼接

方法 参数 返回值 作用 是否改变原数组
concat(value0, value1, ...) 要合并的一个或多个数组/值 新数组 合并多个数组 ❌ 否
slice(start, end) start: 起始索引;end: 结束索引(不包含) 新数组 截取数组的一部分 ❌ 否
join(separator) separator: 分隔符,默认逗号 字符串 将数组合并为字符串 ❌ 否
reverse() 同一数组引用 就地反转数组元素顺序 ✅ 是
sort(compareFn) compareFn: (a, b) => number 同一数组引用 就地排序(默认按字符串升序) ✅ 是
fill(value, start, end) value: 填充值;start: 起始索引;end: 结束索引 修改后的数组 用固定值填充数组指定范围 ✅ 是
let arr1 = [1, 2];
let arr2 = [3, 4];

// concat - 合并
let merged = arr1.concat(arr2, [5, 6]);
console.log(merged); // [1, 2, 3, 4, 5, 6]

// slice - 截取
let sliced = [10, 20, 30, 40, 50].slice(1, 4);
console.log(sliced); // [20, 30, 40]

// join - 转字符串
let words = ['Hello', 'World'];
console.log(words.join('-')); // 'Hello-World'

// reverse - 反转(改变原数组)
let nums = [1, 2, 3, 4, 5];
nums.reverse();
console.log(nums); // [5, 4, 3, 2, 1]

// sort - 排序(改变原数组)
let scores = [40, 100, 1, 5, 25];
scores.sort((a, b) => a - b); // 升序
console.log(scores); // [1, 5, 25, 40, 100]

5. 其他实用方法

方法 参数 返回值 作用 是否改变原数组
flat(depth) depth: 展开深度,默认1 新数组 将嵌套数组拉平 ❌ 否
flatMap(callbackFn) callbackFn: (item, index, array) => 新值 新数组 map后flat一层 ❌ 否
entries() 新的数组迭代器对象 返回键/值对迭代器 ❌ 否
keys() 新的数组迭代器对象 返回索引键的迭代器 ❌ 否
values() 新的数组迭代器对象 返回值的迭代器 ❌ 否
// flat - 数组扁平化
let nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]

// flatMap
let words2 = ['hello', 'world'];
let result = words2.flatMap(word => word.split(''));
console.log(result); // ['h','e','l','l','o','w','o','r','l','d']

// entries - 键值对迭代
let fruits2 = ['apple', 'banana'];
for (let [index, value] of fruits2.entries()) {
    console.log(`${index}: ${value}`);
}

二、Map内置函数详解

Map是键值对存储结构,键可以是任何类型,且按插入顺序排列。在ArkTS中支持泛型。

// 声明泛型Map
let userMap: Map<string, number> = new Map();

Map常用方法速查表

方法 参数 返回值 作用 是否改变原Map
set(key, value) key: 键;value: 值 Map对象本身 添加或更新键值对 ✅ 是
get(key) key: 要查询的键 对应的值,不存在返回undefined 获取键对应的值 ❌ 否
has(key) key: 要检查的键 boolean 判断是否存在某个键 ❌ 否
delete(key) key: 要删除的键 boolean(是否删除成功) 删除指定的键值对 ✅ 是
clear() undefined 清空所有键值对 ✅ 是
size(属性) number 获取键值对数量 ❌ 否
forEach(callbackFn) callbackFn: (value, key, map) => void undefined 遍历Map中的每个键值对 ❌ 否
keys() IterableIterator 返回所有键的迭代器 ❌ 否
values() IterableIterator 返回所有值的迭代器 ❌ 否
entries() IterableIterator<[K, V]> 返回所有键值对的迭代器 ❌ 否

代码示例

let deviceMap: Map<string, number> = new Map();

// set - 添加(可链式)
deviceMap.set('device1', 100)
         .set('device2', 200)
         .set('device3', 300);
console.log(deviceMap.size); // 3

// get - 获取
console.log(deviceMap.get('device1')); // 100

// has - 判断
console.log(deviceMap.has('device2')); // true

// delete - 删除
deviceMap.delete('device3');
console.log(deviceMap.size); // 2

// forEach - 遍历
deviceMap.forEach((value, key) => {
    console.log(`Key: ${key}, Value: ${value}`);
});

// for...of 解构遍历
for (let [key, value] of deviceMap) {
    console.log(`${key} => ${value}`);
}

// keys / values
let keys = Array.from(deviceMap.keys());   // ['device1', 'device2']
let values = Array.from(deviceMap.values()); // [100, 200]

// clear - 清空
deviceMap.clear();
console.log(deviceMap.size); // 0

Map使用注意事项

  1. 对象作为键时需引用一致
let objMap = new Map<object, string>();
let obj1 = { id: 1 };
let obj2 = { id: 1 };
objMap.set(obj1, 'data');
console.log(objMap.get(obj2)); // undefined(不同引用)
  1. JSON序列化需转换
let map = new Map([['key1', 'value1']]);
// 错误:JSON.stringify(map) 返回 '{}'
// 正确做法:
console.log(JSON.stringify(Object.fromEntries(map.entries())));
  1. 类型安全:ArkTS要求明确指定泛型类型,避免使用隐式any。

三、高级用法与避坑指南

3.1 数组方法链式调用

let users = [
    { name: '张三', age: 25, score: 85 },
    { name: '李四', age: 30, score: 92 },
    { name: '王五', age: 22, score: 78 }
];

let totalScore = users
    .filter(user => user.age >= 25)
    .map(user => user.score)
    .reduce((sum, score) => sum + score, 0);
console.log(totalScore); // 177

3.2 鸿蒙UI状态管理中的数组变更检测

@State 监听的是数组引用变化,而非内部元素变更:

@State private list: string[] = ['A', 'B', 'C'];

// ❌ 不会触发UI刷新
this.list[0] = 'X';

// ✅ 重新赋值整个数组或使用变更检测方法
this.list = [...this.list];

3.3 splice删除与内存

splice 返回被删除元素,若不需要可手动解除引用避免内存滞留:

let removed = dataArray.splice(100, 200);
removed = null;

3.4 正确复制数组:浅拷贝与深拷贝

在实际开发中,我们经常需要复制一个数组,但直接使用 = 赋值只是复制了引用(地址),新数组和原数组指向同一块内存,修改新数组会意外改变原数组。

let original = [{ value: 1 }, { value: 2 }];
let copy = original;        // 仅复制引用,地址相同
copy[0].value = 100;
console.log(original[0].value); // 100 ❌ 原数组也被修改了

为了解决这个问题,需要根据数组元素的类型选择合适的拷贝方式。

3.4.1 浅拷贝(只复制第一层)

以下方法都会返回一个新数组,不会改变原数组,但如果数组元素是对象或数组,则这些内部对象仍然是共享引用

方法 示例 特点
slice() let newArr = arr.slice() 最常用
concat() let newArr = arr.concat() 返回合并后的新数组
展开运算符 let newArr = [...arr] 语法简洁
Array.from() let newArr = Array.from(arr) 可同时进行映射

示例:浅拷贝导致内部对象被共享

let original = [{ count: 1 }, { count: 2 }];
let shallow = [...original];          // 浅拷贝

shallow[0].count = 99;
console.log(original[0].count);       // 99 ❌ 原数组也被改了

如果数组元素是基本类型(number, string, boolean, null, undefined),浅拷贝已经足够,因为基本类型是不可变的。

3.4.2 深拷贝(完全独立,修改互不影响)

深拷贝会递归复制所有层级的元素,生成完全独立的新数组。在 ArkTS 中推荐以下方法:

方法一:JSON.parse(JSON.stringify(arr))(最常用,但有局限性)

let original = [{ value: 1 }, { value: 2 }];
let deepCopy = JSON.parse(JSON.stringify(original));

deepCopy[0].value = 999;
console.log(original[0].value); // 1 ✅ 原数组不变

⚠️ 局限性

  • 无法复制 undefined函数Symbol正则表达式 等(会变成 null 或被丢弃)
  • 不能处理循环引用(会报错)
  • 不适用于包含 DateMapSet 等特殊对象的数组

方法二:structuredClone()(推荐,ArkTS 支持)

structuredClone 是现代 JavaScript / ArkTS 内置的深拷贝方法,功能更强大。

let original = [{ date: new Date(), map: new Map([['key', 'value']]) }];
let deepCopy = structuredClone(original);

deepCopy[0].date.setFullYear(2025);
console.log(original[0].date.getFullYear()); // 当前年份 ✅ 不受影响

优点:支持 DateMapSetArrayBuffer 等多数内置对象,也支持循环引用。

方法三:手写递归(适用于复杂定制场景)

如果数组嵌套层次深且包含无法被 structuredClone 处理的特殊对象,可以自己实现递归拷贝。

function deepClone<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj.getTime()) as any;
  if (obj instanceof Map) {
    let newMap = new Map();
    obj.forEach((v, k) => newMap.set(deepClone(k), deepClone(v)));
    return newMap as any;
  }
  if (obj instanceof Set) {
    let newSet = new Set();
    obj.forEach(v => newSet.add(deepClone(v)));
    return newSet as any;
  }
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item)) as any;
  }
  // 普通对象
  let clonedObj = {} as any;
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clonedObj[key] = deepClone(obj[key]);
    }
  }
  return clonedObj;
}

// 使用
let original = [{ a: 1, b: { c: 2 } }];
let deep = deepClone(original);
deep[0].b.c = 999;
console.log(original[0].b.c); // 2 ✅
3.4.3 实际场景选择建议
场景 推荐方式
数组元素全是基本类型(string/number/boolean等) 浅拷贝([...arr]slice()
数组元素是普通对象/数组,不含特殊类型,无循环引用 structuredCloneJSON.parse(JSON.stringify())
数组包含 DateMapSet structuredClone(首选)或手写递归
性能要求极高,数组非常大 浅拷贝(若可接受共享引用)或使用 Immutable 数据结构
3.4.4 鸿蒙开发中的注意事项
  • 在 ArkTS 中,structuredClone 受支持(API 9+),但请确认你的应用目标 API 版本。
  • 使用 JSON 方法时注意:undefinedfunction 会被忽略,如果数据中必须包含这些类型,请改用其他方法。
  • 频繁对大型数组进行深拷贝可能影响性能,建议结合不可变数据模式(如每次返回新数组但复用未修改的部分)来优化。

快速示例对比

// 错误:地址共享
let wrong = original;  
wrong[0].value = 999;  // 原数组也被改

// 正确:浅拷贝(基本类型安全)
let shallow = [...original];   // 若元素是对象,仍会共享

// 正确:深拷贝(完全独立)
let deep = structuredClone(original);
deep[0].value = 999;   // 原数组不变

四、总结速查表

数组是否改变原数组一览

类型 方法 是否改变原数组
增删 push, pop, unshift, shift, splice, reverse, sort, fill, copyWithin ✅ 是
遍历/映射 forEach, map, filter, reduce, reduceRight, some, every ❌ 否
查找 indexOf, lastIndexOf, find, findIndex, includes ❌ 否
转换/拼接 concat, slice, join, flat, flatMap ❌ 否

Map是否改变原Map一览

方法 是否改变原Map
set, delete, clear ✅ 是
get, has, size, forEach, keys, values, entries ❌ 否

希望本文能帮助大家快速掌握鸿蒙ArkTS中数组与Map的操作。如有疑问或补充,欢迎在评论区交流讨论!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐