如何检测哪个操作修改了我的data变量?
我编写了一段代码,其中data是可购买物品列表,user存储用户购买的物品;itemid是要查询的物品ID,itemfeat是该物品的特性列表。calcfeat函数负责在用户购买列表中不存在对应特性时添加该特性,否则提示已存在;checkpurchases函数会在特性值为0时删除整个特性对象,否则将值减1。
现在出现问题:checkpurchases本应只修改user.purchases的值,但实际环境中却同时修改了data中的对应值。我在本地无法复现,但日志显示就是这段修改值的代码导致的,可这段代码并没有直接引用data,想知道怎么定位到底是什么操作修改了data。
代码如下:
var data = [ { id: 'a5', cost: 250, feats: null, css: '' } , { id: 'a6', cost: 350, feats: [ { id: 'size', value: 1} ] } , { id: 'a7', cost: 150, feats: null, css: ''} ]; var itemid = 'a6'; var itemfeat = data.filter(d => d.id === itemid && d.feats).reduce((acc, d) => [...acc, ...d.feats], []); var user = { userid: '45', username: 'john', purchases: [{ id: 'size', value: 1 }]}; calcfeat = function(itemfeat) { itemfeat.forEach(e => { let obj = user.purchases.find(a => a.id === e.id); if (obj) { console.log('item is still present'); } else { user.purchases.push(e); } }); } checkpurchases = function() { if (user.purchases.length != 0) { user.purchases.map(a => { if (a.value == 0) { var nopurchase = user.purchases.filter((b) => { return b.id != a.id; }); user.purchases = nopurchase; } else { console.log(JSON.stringify(user.purchases) + ' before check' + JSON.stringify(data) + '========================================'); a.value = a.value - 1; //this will change [{"id":"size","value":1}] to [{"id":"size","value":0}] inside user.purchases but also in data which I dont intend console.log(JSON.stringify(user.purchases) + ' after check' + JSON.stringify(data) + '========================================'); } }); } } calcfeat(itemfeat); checkpurchases();
问题根源
这是对象引用传递导致的。JavaScript中对象属于引用类型,当你把data里的特性对象(比如data[1].feats[0])直接推送到user.purchases数组时,user.purchases里的元素和data里的对应元素指向的是同一个内存地址。所以修改user.purchases中对象的value属性时,实际上是在修改内存中的同一个对象,自然会影响到data里的对应值。
你的calcfeat函数里执行user.purchases.push(e)时,e就是直接来自data的特性对象,没有做任何拷贝,这就是问题的核心。
定位验证方法
- 在
calcfeat函数推送对象前,打印e和data中对应对象的引用关系:console.log(e === data[1].feats[0]),如果返回true,说明两者是同一个对象。 - 在修改
a.value的代码前后,分别打印user.purchases[0]和data[1].feats[0]的引用对比,确认是否指向同一内存地址。
解决方案
推送对象到user.purchases时,创建对象的副本,避免直接引用data中的原对象:
// 替换原代码中的 user.purchases.push(e); user.purchases.push({...e}); // 浅拷贝,适用于当前无嵌套的对象结构 // 如果对象有深层嵌套属性,需要使用深拷贝:JSON.parse(JSON.stringify(e))
另外,checkpurchases函数中用map遍历却没有返回值,建议换成forEach更符合语义,避免不必要的数组创建:
// 替换原代码中的 user.purchases.map(a => { user.purchases.forEach(a => { // 原有逻辑不变 });
内容的提问来源于stack exchange,提问作者Cain Nuke

