问题引出
前些天小小白在调试前端代码的时候遇到了console.log异步打印的问题。大致过程如下,小小白在调用一个对象中的某个属性的时候控制台报错,显示该值为undefined,于是我使用console.log属性调用之前打印了该对象,结果控制台输出的对象中该属性是正常且有值的。
那为什么我调用该属性却显示没有值呢?
小小白百思不得其解,最终通过查阅资料发现是console.log异步打印的问题,这是console.log本身存在的一个坑。
问题复现
定义一个对象
queryParams: {
pageNum: 1,
pageSize: 3,
userId: undefined,
testParam: undefined
}
页面中打印对象,并修改对象中某个属性的值
console.log('修改之前打印queryParams', this.queryParams);
console.log('修改之前打印testParam', this.queryParams.testParam);
this.queryParams.testParam = 2;
console.log('修改之后打印queryParams', this.queryParams)
console.log('修改之后打印testParam', this.queryParams.testParam);
打印结果如下
注意:此时我没有展开打印出来的queryParams
结合打印的代码和上述打印结果,我们可以分析出,第一行修改之前打印的queryParams中testParam应当为undefined。
但是!!!有趣的事情发生了,当我将打印的对象展开之后,出现的结果让我目瞪口呆,让我们看图说话
明明我们通过第二行打印的结果得知修改之前queryParams中testParam应当为undefined才对啊,为什么当我展开之后却是和修改后的值一样呢?而且如果真如打印结果所示,那我第二行获取修改之前的testParam为什么undefined?又是震惊!!!console.log居然存在这么严重的bug!!!
咳咳~~,其实不是bug哈,当我们对console.log进行深层次分析之后就会明白,它为什么会出现这种情况,别急,继续往下看。
问题分析
出现这个问题的原因其实是因为console.log对于对象的打印不是打印对象变量本身,而是打印的是对象的引用,如下图
queryParams变量中存储的并不是对象真实的值,而是对象在内存中的地址,通过这个地址指向真实对象,相信对于对象在内存中的存储有过了解的小伙伴都不难理解这一知识点。
知道这个之后,就不难理解上述问题了。由于console.log打印的是对象在内存中真实的值,所以当我们在展开控制台打印的queryParams之前,他一直保持着当前对象在内存中的最新状态。也就是说,当我们手动修改了对象的状态之后,控制台打印的未展开的queryParams的值也会随之更新为最新状态。
如何理解这一问题?
我们可以在修改之前加一个定时器,在控制台打印出修改之前的queryParams时将它展开,此时对象在内存中的最新状态就是修改前的。代码如下:
console.log('修改之前打印queryParams', this.queryParams);
console.log('修改之前打印testParam', this.queryParams.testParam);
console.log('等待5s钟再修改testParam');
setTimeout(() => {
this.queryParams.testParam = 2;
console.log('修改之后打印queryParams', this.queryParams)
console.log('修改之后打印testParam', this.queryParams.testParam);
}, 5000)
在等待的间隙我们将修改之前打印queryParams展开,就能得到以下结果
这就是console.log异步打印的原因所在,当我们展开console.log打印的对象之前,该对象始终保持着内存中对象的最新状态,如果此时对对象状态进行了修改,那么无论是何时打印的对象,其内容都会变为最新的内容。
console.log这个特性在某些情况下容易产生误导,那怎么解决呢?往下看
怎么解决
解决方案有多种
第一:将对象JSON序列化
既然console.log打印的是对象当前引用的最新状态,那我们直接将当前对象的最新状态序列化成JSON串进行打印,这样打印的就是该对象当前状态下的值。
第二:深拷贝对象
使用深拷贝(例如使用 lodash
的 cloneDeep
方法)在 console.log
前创建对象的深拷贝。
const _ = require('lodash');
console.log(_.cloneDeep(obj));
注意:此问题不会产生业务层面的bug,只是可能会对使用console.log调试带来误导。
小小白在遇到这个问题的时候觉得很有趣,因此将其记录下来,小伙伴们如果有遇到一样的问题有更好的解决办法的欢迎在评论区跟小小白交流。
bye~~