浏览器调试中console的坑

遇到的坑

今天在调试接口的时候发现一个奇怪的问题:同一个接口同一个用户同一个环境在两个项目中返回的结果不一样。遇到这种情况当然是要把锅甩出去咯,我就把流水拿给了后端同学让帮忙查一下,后端同学竟然说两条请求返回的结果是一样的。诶这就很奇怪了,我明明看console打印出来的日志是不一样。奇怪之下我开始打断点去调试,发现后端返回回来的结果确实是一样的,是前端和console工具共同的问题造成了这个结果。先看表现是怎样的:

1
2
3
4
5
6
7
8
var a = {a: 1}
console.log(a)
// 这时控制台输出 ▶ {a: 1} * 注意这时还没有展开这个对象
a.a = 2
// 这时我们把上面的对象a展开,就会发现展示下面结果
// ▼{a: 1}
// a: 2
// ▶__proto__: Object

这应该是控制在输出log的时候,其实是只保存了一个a的引用,当鼠标要展开这个对象时,再去调用getter方法去目标对象中取值,为了验证这个想法,我利用Object.defineProperty方法去定义一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 方案1
var a = {}
Object.defineProperty(a, 'a', {
writable: false,
value: 1
})
console.log(a)
a.a = 2
// 此时把上面输出的a对象展开,得到{a: 1}

// 方案2
var b = {a: 1}
Object.defineProperty(b, 'a', {
get: function () { return 2},
set: function(newValue){
bValue = newValue;
}
})
console.log(b)
b.a = 3
// 此时把上面输出的a对象展开,得到{a: 2}

从上面两个代码我们就发现,确实当console打印出来时,他只保留了一个对象的引用,当真正展开去查看对象时才去调用getter方法取值。那是每次展开对象时都会去取值嘛?

1
2
3
4
var a = {a: 1}
console.log(a) // 点击展开对象,再收起
a.a = 2
// 再展开上面的对象发现还是1

所以我们发现只有第一次展开对象的时候会去getter他的值,后面收起就真的只是收起来了,再次展开也只是展开对象不会再次去取值。

所以,为什么会出现最开始调试时的那个问题。是因为请求回来之后操作改变了console引用对象的值,导致我们在查看对象时已经不是最初的返回结果了。那其实这样也不是我们理想当中状态,我们console就是为了输出当时的状态,而不是后面更改过的状态。

解决

知道了问题的原因,解决方案其实就是在打印的时候去拷贝一个对象,或者是在返回后不要修改console的对象。我的方法是用了lodash中的拷贝方法,console一个拷贝出来的对象。

1
2
const _response = _.cloneDeep(response)
console.log(_response)

还有一种比较简单的深复制方法:var newObject = JSON.parse(JSON.stringify(oldObject));当然这个方法有一些局限性,比如:如果被拷贝的对象中有function,则拷贝之后的对象就会丢失这个function,如果被拷贝的对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object。

1
2
3
4
5
6
7
8
let a = {
name: 'liyanfeng',
sayName: function() {alert(this.name)},
numberReg: /\d+/
}
let b = JSON.parse(JSON.stringify(a))
// sayName丢失,numberReg变为了对象
console.dir(b) // {name: 'liyanfeng', numberReg: {}}

console的作用

前端调试中离不开console命令,主要是用来记录打印日志便于前端调试,其实console还有一些其他的妙用,比如计数(count)、计时(time)、断言(assert)等等功能,具体可以看以下的文章来学习:

*当时搜索时还发现,console.dir在firefox浏览器上会输出对象当前的一个副本,不会因为后面对象的改变而改变,但是这个在chrome当中却没有效果。

延伸