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

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模式,查看dictscacheDictData里每个元素的地址是否一样即可验证: 可以看到cacheDictData是507,里面的三个元素是511、512、513

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

但是为什么会结果不一样呢,问题就出在filter这个操作,这个操作会生成一个新的集合,然后这时候再赋值给dictsdicts就发生了改变,可以往下再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,在操作对象的时候需要考虑对象引用的传递,需要考虑清楚操作的是新的对象还是原有对象,防止此类问题的发生

相关推荐
古蓬莱掌管玉米的神5 小时前
vue3语法watch与watchEffect
前端·javascript
林涧泣5 小时前
【Uniapp-Vue3】uni-icons的安装和使用
前端·vue.js·uni-app
雾恋5 小时前
AI导航工具我开源了利用node爬取了几百条数据
前端·开源·github
拉一次撑死狗5 小时前
Vue基础(2)
前端·javascript·vue.js
祯民6 小时前
两年工作之余,我在清华大学出版社出版了一本 AI 应用书籍
前端·aigc
热情仔6 小时前
mock可视化&生成前端代码
前端
m0_748246356 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
wjs04066 小时前
用css实现一个类似于elementUI中Loading组件有缺口的加载圆环
前端·css·elementui·css实现loading圆环
爱趣五科技6 小时前
无界云剪音频教程:提升视频质感
前端·音视频
计算机-秋大田7 小时前
基于微信小程序的校园失物招领系统设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计