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

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,在操作对象的时候需要考虑对象引用的传递,需要考虑清楚操作的是新的对象还是原有对象,防止此类问题的发生

相关推荐
Anlici1 小时前
连载小说大学生课设 需求&架构
前端·javascript·后端
2501_938769992 小时前
React Server Components 进阶:数据预取与缓存
前端·react.js·缓存
蒜香拿铁2 小时前
Angular【基础语法】
前端·javascript·angular.js
xiaoxiao无脸男3 小时前
纯css:一个好玩的按钮边框动态动画
前端·css·css3
rookie_fly3 小时前
基于Vue的数字输入框指令
前端·vue.js·设计模式
元直数字电路验证3 小时前
ASP.NET Core Web APP(MVC)开发中无法全局配置 NuGet 包,该怎么解?
前端·javascript·ui·docker·asp.net·.net
rexling14 小时前
【Spring Boot】Spring Boot解决循环依赖
java·前端·spring boot
我有一棵树4 小时前
Vue 项目中全局样式的正确写法:不要把字体和主题写在 #app 上
前端·javascript·vue.js
Luna-player4 小时前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本,解决方法
前端·npm·node.js
悢七4 小时前
windows npm打包无问题,但linux npm打包后部分样式缺失
linux·前端·npm