开发中经常使用console.log来打印信息,一般而言,其输出基本上符合预期,以至于没去深究其执行机制,直到遇到了两次console.log输出与预期不同,但偏偏页面展示的却与console.log打印出来的信息符合的情况,因此,将问题定位到是数据处理有问题,花费了大量的时间去确认是否数据处理出现问题,而没有考虑到可能是自己对console.log输出机制的不了解,导致解决问题的方向出错。
示例
考虑下面代码中console.log输出什么?
var obj = {
name: 'Bob',
age: '19',
};
console.log(obj); // 打印什么?
obj.name = 'change-bob'
在未展开obj对象时,打印如下:
当展开obj对象时:
为什么会出现这个异常输出?
我们知道,JS中对象是引用类型,每次使用对象时,都只是使用了对象在堆中的引用。
当我们在使用obj.name = 'change-bob'改变对象的属性值时,它在堆中name的值也变成了'change-bob',而当我们不展开对象看的时候,console.log打印的是对象当时的快照,所以我们看到的name属性值是没改变之前的'Bob',展开对象时,它其实是重新去内存中读取对象的属性值,所以当我们展开对象后看到的name属性值是'change-bob'。
为什么会出现这种情况,在《你不知道的JavaScript中卷》中有提到:
我们通常认为恰好在执行到console.log(..) 语句的时候会看到a对象的快照,打印出类似于{ index: 1 } 这样的内容,然后在下一条语句a.index++ 执行时将其修改,这句的执行会严格在a 的输出之后。
多数情况下,前述代码在开发者工具的控制台中输出的对象表示与期望是一致的。但是,这段代码运行的时候,浏览器可能会认为需要把控制台I/O 延迟到后台,在这种情况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示{ index: 2 }。
到底什么时候控制台I/O 会延迟,甚至是否能够被观察到,这都是游移不定的。
所以如果在调试的过程中遇到对象在console.log(..) 语句之后被修改,可你却看到了意料之外的结果,要意识到这可能是这种I/O 的异步化造成的。
开发中遇到的问题
在使用react开发时,遇到如下问题:
// 假设原state = {name: 'bob', age: 10}
change() {
this.setState({
name: 'change-bob',
age: 100
}, () => {
// 这里打印出来的是: {name: 'change-bob', age:100}
console.log(this.state);
})
}
// 在一个按钮的事件处理函数中,将this.state传给子组件,并打印this.state
handle() {
console.log(this.state);
}
在实际的测试中发现,handle()打印出的state是{name: 'bob', age: 10},在子组件中打印的也是一样,但是,在handle()中使用debugger时,可以看到state是{name: 'change-bob', age:100},因此,在实际开发中,console.log打印出的数据并不是很可靠,当与预期不一致时,最好使用debugger打断点确认下实际的值
结论
由此可见,console.log打印出来的内容并不是一定百分百可信的内容。一般对于基本类型number、string、boolean、null、undefined的输出是可信的。
但对于Object等引用类型来说,则就会出现上述异常打印输出。
所以对于一般基本类型的调试,调试时使用console.log来输出内容时,不会存在坑。但调试对象时,最好还是使用打断点 (debugger) 这样的方式来调试更好。