记录一次对象引用误操作问题

1. 问题描述

在前端开发的过程中碰到了这样的一个问题,首先有一个方法是负责去查字典,如果查询到了就把该信息缓存在前端,然后用的时候就先判断缓存有没有数据,如果有就直接读取,如果没有再向后端查询。在这个过程中发生了误操作缓存到前端的数据,导致其他地方使用的时候出现了错误

2. 案例分析

2.1 代码还原

用一段代码来描述这个问题

js 复制代码
<template>
  <div>
    <button @click="handleTest">测试</button>
  </div>
</template>

<script lang="ts" setup>
import {ref} from "vue";
// 模拟调用后端获取到缓存在前端的的数据
const cacheDictData = [{"value": "1"}, {"value": "2"}, {"value": "3"}]

const handleTest = () => {
  const dicts = ref(cacheDictData)
  console.log('原始数据:')
  console.log('----------cacheDictData----------')
  console.table(cacheDictData)
  console.log('----------dicts----------')
  console.table(dicts.value)

  dicts.value.forEach(item => item.value = Number(item.value))
  dicts.value = dicts.value.filter(item => item.value !== 2)

  console.log('误操作之后的数据:')
  console.log('----------cacheDictData----------')
  console.table(cacheDictData)
  console.log('----------dicts----------')
  console.table(dicts.value)
}

</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">

</style>

2.2 功能描述

页面很简单,就一个按钮

这段代码的作用本意很简单,在页面上新创建一个变量dicts,用来存储从缓存中获取到的数据。然后需要先把拿到的数据的value转换成Number类型,然后再进行过滤,最终拿到结果。在这个过程中,页面上定义的dicts确实是按照预期的获取到数据并且过滤了。但是因为其他地方也是使用相同的缓存数据cacheDictData,这里操作数据的方式不对,导致了其他地方出现了异常。

现在点击按钮之后观察数据的变化: 可以看到dicts是预期之内的结果,但是cacheDictData的数据类型,居然也变成了Number

2.3 原因分析

分析过程也是坎坷曲折,一开始想的是既然操作的都是dicts,如果数据类型变了,那么按道理来说value为2的那项数据应该也会在cacheDictData中被一起删除。就是因为这个导致思考了很久。后面发现是因为dicts是在页面中新建的,也就是说dicts.value = dicts.value.filter(item => item.value !== 2)这个操作,实际是对dicts的引用起作用,但是操作dicts.value.forEach(item => item.value = Number(item.value))的时候,操作的是cacheDictData里面每一项的引用,从而导致了不知不觉中把cacheDictData的数据类型给改了,导致问题。

3. 后端验证

分析了这个问题的原因,肯定要对这个问题进行一个确认,前端看不了数组和对象的引用,那么就换一下,用java去模拟这个过程,看看操作的对象,对上述分析过程进行验证

Dict类

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dict {

    private String dict;

    private String value;

}

测试类

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Dict> cacheDictData = new ArrayList<>();
        cacheDictData.add(new Dict("value", "1"));
        cacheDictData.add(new Dict("value", "2"));
        cacheDictData.add(new Dict("value", "3"));
        List<Dict> dicts = cacheDictData;
        System.out.println("操作之前");
        System.out.println("dicts = " + dicts);
        System.out.println("cacheDictData = " + cacheDictData);

        dicts.forEach(item -> item.setValue(item.getValue() + "操作之后"));
        dicts = dicts.stream().filter(dict -> !"2操作之后".equals(dict.getValue())).collect(Collectors.toList());

        System.out.println("操作之后");
        System.out.println("dicts = " + dicts);
        System.out.println("cacheDictData = " + cacheDictData);
    }
}

查看结果,可以看到dicts集合的元素变少了,并且value也变了,同时cacheDictData的值也变了

java 复制代码
操作之前
dicts = [Dict(dict=value, value=1), Dict(dict=value, value=2), Dict(dict=value, value=3)]
cacheDictData = [Dict(dict=value, value=1), Dict(dict=value, value=2), Dict(dict=value, value=3)]
操作之后
dicts = [Dict(dict=value, value=1操作之后), Dict(dict=value, value=3操作之后)]
cacheDictData = [Dict(dict=value, value=1操作之后), Dict(dict=value, value=2操作之后), Dict(dict=value, value=3操作之后)]

用debug模式,查看dicts和cacheDictData里每个元素的地址是否一样即可验证: 可以看到cacheDictData是507,里面的三个元素是511、512、513

再查看dicts,可以看到一开始的时候跟cacheDictData是一样的

但是为什么会结果不一样呢,问题就出在filter这个操作,这个操作会生成一个新的集合,然后这时候再赋值给dicts,dicts就发生了改变,可以往下再debug验证。等执行完filter之后再查看dicts,发现dicts的地址已经发生了改变,但是里面每一个元素的地址还是不变的,这跟结果也刚好符合

4. 解决

其实这个解决的方法也很简单,可以通过深拷贝来获得一个新的对象即可 例如我这里把直接获取改为通过JSON.parse(JSON.stringify(cacheDictData))来获取一个新的对象

JS 复制代码
// const dicts = ref(cacheDictData)
const dicts = ref(JSON.parse(JSON.stringify(cacheDictData)))

这样就不会影响原有数据

当然前端也可以使用其他库,比如lodash,VueUse等,我这里就不过多赘述

5. 总结

经过解决这次的问题,无论是js还是java,在操作对象的时候需要考虑对象引用的传递,需要考虑清楚操作的是新的对象还是原有对象,防止此类问题的发生

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端