起因
后端的接口给我返回了一个res对象,里面包含有一个list数组,根据业务需求我需要对list数组进行过滤。通过打印过滤前后端接口的输出结果res,以及过滤后res对象,发现两个对象一模一样,过滤前输出的res也没有包含过滤的内容,但是如果执行了过滤逻辑,会打上一个标记,标记是存在的。我一度怀疑是后端接口的问题,找了后端接口大哥对了下,接口日志中也是存在需要过滤的内容的。通过多次对照前端未过滤前输出结果与后端接口大哥给的输出结果,一直找不到过滤的内容,搞得焦头烂额,一度以为三体中的智子瞄上我了,想要锁死我的技术。
代码类似于如下:
let person = {
name:'小明',
age:23,
grade:6
}
console.log('before operation',person)
person.name = '小缸'
console.log('after operation',person)
浏览器中输出结果:
person对象展开前:
person对象展开后:
操作person对象前与对象后,输出person对象中name都为操作后的小缸。
排查
通过前端输出异常的检索,看了几篇文章,得出结论是:展开前的输出是对象打印时的快照,展开后直接读取的是堆中引用对象,所以person对象中name都为小缸
以下是更详细的分析:
- console.log()函数不是属于javascript中正式的,而是由宿主环境添加到javascript,每一个宿主环境对console.log的实现可能不同。另外该函数是一个I/O操作,非常耗时,浏览器可能会对其进行优化例如异步操作,后续对res处理代码将在IO操作之前执行,从而导致操作前的输出与操作后的输出一致。
- js中数据分为两种类型,引用类型与基本类型
* 基本类型例如字符串、number、boolean类型,它们无法进行引用,都需要重新开启空间进行存储
* 引用类型是为了减少空间消耗而产生的,例如原型链,里面将该类对象公共属性及公共函数都放在原型链上,增加逻辑复用,使代码更简洁。
因为后端返回的是一个res对象,我也是直接在该res对象上进行过滤的,所以引用的对象一直都没有发生改变,它一直位于堆区的一个位置上。
解决方法
解决方法如下,序号越小越推荐:
- 最好的选择是在JavaScript 调试器中使用断点,而不要依赖控制台输出。
- 次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过JSON.stringify(..)。
- 拷贝一个新的结果对象,对新的结果对象上进行操作