console.log的输出机制

1,121 阅读3分钟

开发中经常使用console.log来打印信息,一般而言,其输出基本上符合预期,以至于没去深究其执行机制,直到遇到了两次console.log输出与预期不同,但偏偏页面展示的却与console.log打印出来的信息符合的情况,因此,将问题定位到是数据处理有问题,花费了大量的时间去确认是否数据处理出现问题,而没有考虑到可能是自己对console.log输出机制的不了解,导致解决问题的方向出错。

示例

考虑下面代码中console.log输出什么?

var obj = {
   name: 'Bob',
   age: '19',
};
console.log(obj); // 打印什么?
obj.name = 'change-bob'

在未展开obj对象时,打印如下:

image.png

当展开obj对象时:

image.png

为什么会出现这个异常输出?

我们知道,JS中对象是引用类型,每次使用对象时,都只是使用了对象在堆中的引用。

当我们在使用obj.name = 'change-bob'改变对象的属性值时,它在堆中name的值也变成了'change-bob',而当我们不展开对象看的时候,console.log打印的是对象当时的快照,所以我们看到的name属性值是没改变之前的'Bob',展开对象时,它其实是重新去内存中读取对象的属性值,所以当我们展开对象后看到的name属性值是'change-bob'

为什么会出现这种情况,在《你不知道的JavaScript中卷》中有提到:

image.png

我们通常认为恰好在执行到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) 这样的方式来调试更好。