微信小程序开发陷阱:数组引用类型导致的数据同步修改问题
问题背景
在开发微信小程序时,我遇到了一个看似诡异的问题:明明已经将完整的标记数组保存到了 markersAll 中,结果打印出来却发现数据少了。具体表现为:
- 从接口获取到 3 条打卡记录数据
- 将这些数据转换为地图标记数组 markers (包含 3 个标记)
- 把 markers 赋值给 markersAll 并打印
- 对 markers 进行裁剪操作(只保留起点和终点)
- 再次打印 markersAll ,发现它也变成了只有 2 个标记!
问题原因
JavaScript 引用类型的特性
问题的根源在于 JavaScript 中数组是引用类型 。当执行 this.setData({ markersAll: markers }) 时, 并不是创建了一个新的数组副本 ,而是将 markers 数组的引用地址赋值给了 markersAll 。
这意味着 markersAll 和 markers 指向内存中的同一个数组对象。因此,当后续执行 markers.splice() 操作修改 markers 时, markersAll 自然也会同步变化。
代码重现
// 1. 从接口获取数据并转换为标记数组
let markers = res.data.list.map(item => ({
id: Date.now(),
latitude: item.latitude,
longitude: item.longitude,
iconPath: null,
width: 20,
height: 20,
anchor: { x: 0.5, y: 0.5 }
}));
// 此时 markers 有 3 个元素
// 2. 保存到 markersAll
this.setData({
markersAll: markers // 这里只是传递引用,不是拷贝
});
console.log(this.data.markersAll.length); // 打印 3
// 3. 修改 markers
markers.splice(1, 1); // 删除中间元素
console.log(this.data.markersAll.length); // 打印 2!被意外修改了
解决方案
使用深拷贝创建独立副本
要解决这个问题,需要在赋值时创建数组的 深拷贝 ,确保 markersAll 和 markers 指向不同的数组对象。
方法:JSON 序列化/反序列化
这是一种简单有效的深拷贝方法,适用于大多数场景:
// 修改前
this.setData({
markersAll: markers
});
// 修改后
const markersAll = JSON.parse(JSON.stringify(markers));
this.setData({
markersAll
});
原理
- JSON.stringify(markers) 将数组转换为 JSON 字符串
- JSON.parse(...) 将 JSON 字符串解析回新的数组对象
- 这样就创建了一个完全独立的数组副本,修改 markers 不会影响 markersAll
代码优化建议
1. 明确分离数据用途
在处理数据时,应根据不同用途创建独立的数据副本:
- markersAll :存储完整的标记数据
- markers :用于地图显示(可能需要裁剪或处理)
2. 选择合适的拷贝方法
-
浅拷贝:适用于简单数组(元素为基本类型)
const copy = [...original]; // 扩展运算符 const copy = original.slice(); // slice 方法 -
深拷贝:适用于复杂数组(元素为对象)
// 方法1:JSON 序列化(推荐) const copy = JSON.parse(JSON.stringify(original)); // 方法2:递归拷贝(适用于包含函数等特殊类型的情况) function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Array) return obj.map(item => deepClone(item)); const clonedObj = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; }
3. 避免频繁修改数组
在小程序开发中,频繁修改数组并调用 setData 会影响性能。建议:
- 先在内存中完成所有数据处理
- 最后一次性调用 setData 更新界面
实际效果
修改后,代码执行流程变为:
- 创建原始 markers 数组(3 个元素)
- 深拷贝创建 markersAll (独立数组,3 个元素)
- 裁剪 markers 数组(变为 2 个元素)
- 打印 markersAll ,仍然是 3 个元素(数据保持完整)
总结
这个问题提醒我们:在 JavaScript 开发中, 引用类型的赋值操作需要特别小心 。尤其是在处理数组、对象等复杂数据结构时,要明确是否需要创建副本,避免因引用传递导致的数据意外修改。
通过使用深拷贝创建独立的数据副本,可以有效避免这类问题,让代码逻辑更清晰、更可预测。这不仅适用于微信小程序开发,也是前端开发中的通用最佳实践。
关键点回顾
- 核心问题:JavaScript 中数组是引用类型,直接赋值仅传递内存地址,修改原数组会同步影响赋值后的变量。
- 解决方案:使用深拷贝(JSON 序列化/反序列化、递归拷贝)创建独立数组副本,浅拷贝仅适用于元素为基本类型的简单数组。
- 优化建议:分离不同用途的数据副本,减少小程序中
setData的调用频次,提升性能和代码可维护性。
