码农日记

薄洪涛的个人博客

JS浅拷贝导致的聊天信息丢失问题

问题背景

我们做了一个基于vue的H5在线问诊项目,集成了融云IM,每次进入到会话页面的时候,需要恢复历史聊天数据,我们的历史聊天数据做了三层缓存,内存-->浏览器缓存(LocalStorage)----> 服务器数据库(每次收发信息融云服务器会转发到我们的服务器然后存到数据库),并且我们开发了获取聊天记录的接口,进入到页面的时候,我们的恢复历史聊天数据的部分策略如下:

  1. 链接融云,若融云推送未读消息或者本地localstorage中有此会话数据,存内存

  2. 在1.5s之后,判断加载到内存中的数据,若内存数据为空或者内存中的数据不是当前会话的聊天数据,请求接口获取数据

问题在这里,当我判断内存中的数据是否是当前会话的聊天数据时,代码是这么写的

// that.$store.state.answer-->表示内存中的历史聊天数据
let messageHistory = that.$store.state.answer 
if (messageHistory.length === 0) {
  that.$store.state.answer = []
  that.getChatRecord()
} else if (messageHistory.length > 0) {
  let tmp = messageHistory.pop()
  if (tmp.targetId.toString() !== that.targetId) {
    console.log('内存中数据不是当前会话的数据丢弃')
    that.$store.state.answer = []
    that.getChatRecord()
  } else {
    console.log('无需加载')
  }

此代码执行之后,我发现会丢消息!!!

明明localstorage中有10条消息,内存读出来之后就会变成9条,少一条,大佬们可能一眼看出来问题所在,没错就是下面这行代码的原因

let tmp = messageHistory.pop()

这里的messageHistory实际上和that.$store.state.answer 都指向同一块内存地址,对messageHistory做了pop操作,也就是内存中的数据删除了一条,所以会丢消息

我们知道,js的数据类型Number,string,undefined,null,bool,object 六种,对前五种,都是在内存中直接存储数据的,所以对象复制相当于新开辟一块内存空间,所以深拷贝和浅拷贝没有区别, 如果数据类型是Object,他在数据空间中真正存储的一个地址,数据都是存储在另外一个地方,通过存储的那个地址可以访问,所以对象的简单拷贝就只是仅仅在新开辟的数据空间中复制了那个地址,所以如果对原来对象里面的数进行改变,新的对象也会受到影响。

深拷贝Object的方法如下:

  1. ES6 的扩展运算符(…)

  2. Object.assign()

  3. JSON.parse(JSON.stringify(obj))

QQ20201119-232117@2x.png

特别注意:

第一,二方法无法拷贝多重嵌套

第三种方法针对undefined, function , regExg是无法拷贝的

因此我们实现一个递归拷贝方法

// 定义一个深拷贝函数  接收目标target参数function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;}

ok,到此结束

休息休息睡觉睡觉,晚安大佬们

IMG_3420.jpg

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.3

版权所有 | 转载请标明出处