云计算百科
云计算领域专业知识百科平台

# Chrome Console 打印对象和 Object.keys() 不一致?原来是 Console 的「延迟求值」导致的

Chrome Console 打印对象和 Object.keys() 不一致?原来是 Console 的「延迟求值」导致的

前言

大家好!我是小尤~。最近排查一个前端 Bug 的时候,遇到了一个特别迷惑的问题。

控制台打印出来的对象里面明明有数据,但是执行 Object.keys() 却返回空数组。

例如:

console.log(obj);

console.log(Object.keys(obj));

控制台结果却是:

{}

但是点开 console.log(obj) 后,却能看到:

{
id: 1,
name: "Tom",
age: 18
}

第一反应肯定是:

Chrome Console 出 Bug 了吗?

其实并不是,而是 Chrome Console 一个非常经典的坑——Lazy Evaluation(延迟求值)。


一、问题现象

假设有下面这段代码:

const obj = {};

console.log(obj);

setTimeout(() => {
obj.name = "Tom";
obj.age = 18;
}, 1000);

console.log(Object.keys(obj));

运行结果:

[]

但是当一秒以后,再展开第一个 console.log(obj),看到的却是:

{
name: "Tom",
age: 18
}

很多人第一反应:

Object.keys() 为什么没有获取到?

实际上,它获取的时候对象确实还是空的。


二、Chrome Console 为什么会这样?

Chrome DevTools 在打印对象时,并不会立刻把对象内容序列化下来。

它打印的是:

对象引用(Reference)

也就是说:

console.log(obj);

实际上保存的是:

obj 的内存地址

而不是:

obj 当前的值

等你以后再展开这个对象时,Chrome 才会去读取对象当前的数据。

整个流程如下:

console.log(obj)


保存对象引用


对象后续发生修改


点击展开对象


Chrome 再读取对象内容

因此:

你看到的是

展开时的数据

而不是

打印时的数据

这就是所谓的:

Lazy Evaluation(延迟求值)


三、为什么 Object.keys() 不一样?

来看下面的代码:

const obj = {};

console.log(obj);

console.log(Object.keys(obj));

obj.name = "Tom";

执行顺序实际上是:

第一步:

console.log(obj);

这里只保存引用。

第二步:

Object.keys(obj);

立即执行。

此时对象还是:

{}

所以得到:

[]

第三步:

obj.name = "Tom";

对象发生变化。

第四步:

你展开 Console。

Chrome 再读取对象。

于是看到:

{
name: "Tom"
}

所以出现了:

console.log(obj)
↓↓↓

{name:"Tom"}

但是

Object.keys(obj)

↓↓↓

[]

其实两者都没有错,只是观察的时间点不同。


四、真实开发中最容易踩坑的场景

例如:

const cache = {};

fetchData().then(data => {
cache.data = data;
});

console.log(cache);

网络请求回来之前:

cache = {}

但是等你过几秒再展开:

{
data: …
}

很多人误以为:

fetch 已经执行完了。

其实不是。

只是 Console 显示的是:

当前对象。


再比如:

const state = {
list: []
};

console.log(state);

state.list.push(1);

展开时:

{
list:[1]
}

但打印时其实:

list=[]


五、如何打印对象当时的真实值?

如果希望看到打印瞬间的数据,而不是后续变化后的数据,可以使用以下几种方式。

方法一:结构化克隆(推荐)

浏览器支持 structuredClone 时:

console.log(structuredClone(obj));

打印的是对象当前的快照。


方法二:JSON 序列化

console.log(JSON.parse(JSON.stringify(obj)));

或者:

console.log(JSON.stringify(obj));

优点:

  • 能固定打印时的数据。

缺点:

  • 会丢失:

    • Function
    • Symbol
    • undefined
    • Date(会变成字符串)
    • Map
    • Set

方法三:展开对象

console.log({
obj
});

对于浅对象来说,这也是一个不错的方法。

但是:

const obj = {
user: {
name: "Tom"
}
};

console.log({
obj
});

里面的

user

依旧是引用。

因此:

深层对象依然可能发生变化。


方法四:使用 console.table()

对于数组或者对象列表:

console.table(list);

调试体验通常比直接打印对象更直观。


六、Chrome 官方对此是怎么设计的?

Chrome DevTools 采用对象引用而不是对象快照,主要有两个原因:

1. 性能更高

如果每一次:

console.log(bigObject);

都立即深拷贝整个对象,

那么:

100MB 对象

打印一次,

浏览器就需要复制一次。

这会严重影响调试性能。


2. 可以实时查看对象变化

例如:

const obj = {};

console.log(obj);

obj.a = 1;
obj.b = 2;

展开以后:

{
a:1,
b:2
}

开发者可以方便地观察对象后续的变化,而不用重新打印。


七、总结

Chrome Console 打印对象与 Object.keys() 看起来不一致,并不是浏览器出错,而是 Console 打印的是对象引用,而不是打印时的对象快照。

记住下面这几点,就能避免很多调试误区:

  • console.log(obj) 默认打印的是对象引用,展开时才读取最新值。
  • Object.keys(obj)、Object.entries(obj) 等方法会在调用时立即计算,因此反映的是当时的对象状态。
  • 如果需要记录对象在某一时刻的真实内容,可以使用 structuredClone(obj)、JSON.parse(JSON.stringify(obj)) 或浅拷贝等方式生成快照。
  • 遇到 Console 与代码执行结果看似矛盾时,优先考虑是否是 Lazy Evaluation(延迟求值) 导致的,而不是怀疑 JavaScript 本身。

参考示例

const obj = {};

console.log(obj); // 打印对象引用

console.log(Object.keys(obj)); // []

obj.name = "Tom";
obj.age = 18;

最终控制台可能看到:

console.log(obj)

{
name: "Tom",
age: 18
}

Object.keys(obj)

[]

两者并不矛盾,只是观察的时间点不同而已。

理解了这一点,以后调试对象时就不会再被 Chrome Console "骗"了。

赞(0)
未经允许不得转载:网硕互联帮助中心 » # Chrome Console 打印对象和 Object.keys() 不一致?原来是 Console 的「延迟求值」导致的
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!