《别再直接赋值了!鸿蒙ArkTS数组与Map函数全解析(含深浅拷贝避坑)》
前言
在鸿蒙应用开发中,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使用注意事项
- 对象作为键时需引用一致
let objMap = new Map<object, string>();
let obj1 = { id: 1 };
let obj2 = { id: 1 };
objMap.set(obj1, 'data');
console.log(objMap.get(obj2)); // undefined(不同引用)
- JSON序列化需转换
let map = new Map([['key1', 'value1']]);
// 错误:JSON.stringify(map) 返回 '{}'
// 正确做法:
console.log(JSON.stringify(Object.fromEntries(map.entries())));
- 类型安全: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或被丢弃) - 不能处理循环引用(会报错)
- 不适用于包含
Date、Map、Set等特殊对象的数组
方法二: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()); // 当前年份 ✅ 不受影响
优点:支持 Date、Map、Set、ArrayBuffer 等多数内置对象,也支持循环引用。
方法三:手写递归(适用于复杂定制场景)
如果数组嵌套层次深且包含无法被 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()) |
| 数组元素是普通对象/数组,不含特殊类型,无循环引用 | structuredClone 或 JSON.parse(JSON.stringify()) |
数组包含 Date、Map、Set 等 |
structuredClone(首选)或手写递归 |
| 性能要求极高,数组非常大 | 浅拷贝(若可接受共享引用)或使用 Immutable 数据结构 |
3.4.4 鸿蒙开发中的注意事项
- 在 ArkTS 中,
structuredClone受支持(API 9+),但请确认你的应用目标 API 版本。 - 使用
JSON方法时注意:undefined、function会被忽略,如果数据中必须包含这些类型,请改用其他方法。 - 频繁对大型数组进行深拷贝可能影响性能,建议结合不可变数据模式(如每次返回新数组但复用未修改的部分)来优化。
快速示例对比:
// 错误:地址共享
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的操作。如有疑问或补充,欢迎在评论区交流讨论!
更多推荐
所有评论(0)