普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月17日首页

console.log 骗了我一整个通宵:原来它才是时间旅行者

作者 kyriewen
2026年3月17日 18:30

引言

“这个 bug 我明明修好了,为什么控制台还在报错?”

凌晨三点,我盯着屏幕上的代码,眼袋比眼睛还大。明明我已经在 15 行打印了 user.name,显示的是 '张三';到 30 行修改了 user.name = '李四',然后又在 45 行打印了一次,结果控制台第一次打印的地方展开一看,居然也变成了 '李四'

那一刻我差点把电脑吃了——难道代码在时间旅行?还是说 JavaScript 引擎有自己的想法?

直到隔壁工位的老王路过,瞄了一眼我的屏幕,幽幽地说:“小伙子,你也被 console.log 骗了吧?”

一、案发现场:被篡改的历史记录

先来看一段“案发代码”:

const user = { name: '张三', age: 18 };
console.log(user); // 打印 user
user.name = '李四';
console.log(user); // 打印修改后的 user

你觉得控制台会输出什么?按照常理,应该是两次不同的对象:第一次 {name: '张三', age: 18},第二次 {name: '李四', age: 18}

但如果你在 Chrome 控制台里运行,展开第一次打印的那个对象,你会发现——它也是 {name: '李四', age: 18}!仿佛历史被篡改了一样。

console.log 陷阱示意图转存失败,建议直接上传图片文件(这里可以配个图:一个侦探看着控制台,气泡里显示“李四”)

二、为什么 console.log 会说谎?

2.1 凶手是谁?

答案是:引用类型 + 控制台的“惰性”展示

当你执行 console.log(user) 时,浏览器并没有立刻把 user 对象的快照保存下来,而是保存了对象的引用。在控制台的界面上,对象是可展开的,当你点击展开图标时,控制台才会去读取当前内存中该对象的属性值

也就是说,console.log 打印的是一个“活的”对象——它像一台摄像机,记录的不是当时的照片,而是一个实时直播的摄像头。等你点开看的时候,看到的是直播画面,而非当时的回放。

2.2 基本类型为什么没问题?

let name = '张三';
console.log(name); // '张三'
name = '李四';
console.log(name); // '李四'

这里打印的都是基本类型,不会出现篡改历史的问题,因为基本类型是直接存储值,没有引用关系。控制台直接显示当时的字符串值。

2.3 浏览器们的小心思

  • Chrome/Edge:上面描述的行为最常见。你第一次打印的对象,展开后可能会显示最新的值。
  • Firefox:早期版本也有类似问题,但现在似乎在打印时会对对象进行“快照”?具体版本有差异。
  • Safari:表现也不同,有时会保留快照。

所以跨浏览器调试时更要小心——你以为 Safari 没毛病,结果 Chrome 给你来个篡改。

三、真实案例:因为一个 console.log 通宵加班

我曾经维护过一个老项目,有一个函数负责更新用户信息,其中有一段:

function updateUser(user) {
  console.log('更新前:', user); // 调试用
  user.name = '新名字';
  console.log('更新后:', user); // 调试用
  saveToServer(user);
}

当时我发现控制台里两个 log 展开后 name 都是 '新名字',于是以为 saveToServer 之前 user 已经被改过了,所以怀疑其他代码也修改了 user 引用。我在整个项目里搜索,一无所获。

后来我用 JSON.stringify 打印:

console.log('更新前:', JSON.stringify(user));

终于看到真实的“当时的值”是旧名字。原来 user 对象根本没有被外部修改,是 console.log 骗了我!

四、如何让 console.log 说真话?

4.1 快照大法:深拷贝

在打印前把对象深拷贝一份:

console.log('user 当时的值:', JSON.parse(JSON.stringify(user)));

注意:这种方法无法处理循环引用、函数、undefined、Symbol 等,但对于普通对象足够了。

4.2 展开运算符?小心!

console.log({ ...user });

这样会创建一个新的对象,但它的属性值如果是引用类型,仍然是指向原对象的引用。比如 user.friends 是一个数组,展开后 friends 还是原来的数组,之后修改 user.friends.push('王五'),你打印的那个副本里的 friends 也会变。所以只适用于一层浅拷贝。

4.3 用 console.table 打印表格

对于数组或对象,console.table 会生成一个表格,它会取打印时刻的值,但同样可能受引用影响?实际上 console.table 也是读取当前属性值,所以如果之后修改了原始对象,表格里的数据不会自动更新(因为已经渲染成静态表格了)。这一点比展开对象要可靠。

4.4 使用断点 debugger

最好的办法:直接打断点,在 Sources 面板里查看作用域中的变量值,那是真正的“当时的值”。

debugger; // 代码执行到这里会暂停,你可以慢慢看变量

4.5 自定义一个 safeLog

function safeLog(...args) {
  args.forEach(arg => {
    if (typeof arg === 'object' && arg !== null) {
      console.log(JSON.parse(JSON.stringify(arg)));
    } else {
      console.log(arg);
    }
  });
}

五、其他类似的“时间旅行”陷阱

5.1 数组的 console.log

同样的问题,数组也是对象。

const arr = [1, 2, 3];
console.log(arr); // 展开后可能变成 [1,2,3,4]
arr.push(4);

5.2 异步中的闭包

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 全是 3
  }, 100);
}

这不是 console 的问题,而是闭包捕获了同一个变量。但也是常见的“以为当时的值是 0,1,2”的坑。

5.3 事件监听中的“旧”数据

let count = 0;
button.addEventListener('click', () => {
  console.log(count); // 每次点击打印最新的 count,而不是绑定时的值
});

这也不是 console 的问题,但同样是“值”与“引用”的区别。

六、总结:别太相信 console.log,它只是个演员

console.log 是我们调试的利器,但它也有自己的脾气。理解它的行为,才能避免在 bug 排查时被误导。

  • 记住:打印对象时,控制台保留的是引用,展开时看到的是当前值。
  • 对策:深拷贝、console.table、断点,或者打印基本类型。
  • 心态:遇到奇怪现象,先怀疑工具,再怀疑代码。

最后,分享一个老程序员的玩笑:“当你把 console.log 删干净之后,bug 就消失了。”——有时候,真的是 console.log 在搞鬼。

每日一问:你在调试时还遇到过哪些让人抓狂的“假象”?是 console.log 的延时?还是 sourcemap 错位?欢迎在评论区吐槽,让我们一起长点记性!


(本文虚构故事如有雷同,纯属你也经历过)

在浏览器控制台调试的 6 个秘密技巧

作者 冴羽
2026年3月17日 18:19

有一些神奇的快捷键只在浏览器 DevTools 控制台中工作,但它们绝对是调试时的秘密武器。

技巧 1:$0:最后选中的元素

单击“元素”选项卡中的任意元素,然后切换到控制台:

$0; // 查看元素
$0.style.backgroundColor = "red"; // 修改样式
$0.classList.add("highlight"); // 添加类名

更棒的是:$1$2$3$4 会引用最后选中的 5 个元素。非常适合快速比较或操作多个元素。

技巧 2:$$:更优的选择器

忘掉 document.querySelectorAll() 吧,以后使用 $$:

// 旧方式
Array.from(document.querySelectorAll(".product-card")).forEach((card) => console.log(card.textContent));

// 新方式
$$(".product-card").forEach((card) => console.log(card.textContent));

$$() 返回一个真正的数组(而非 NodeList),因此你可以立即使用数组方法。无需再用 Array.from() 包装一层。

技巧 3:$:快速 querySelector

$("#header"); // 等同于 document.querySelector('#header')
$(".btn-primary"); // 等同于 document.querySelector('.btn-primary')

注意: 这仅在 jQuery 未加载时有效。

如果加载了 jQuery,$ 就是 jQuery。

技巧 4:$_: 最后一个结果

2 + 2; // 4

$_ * 10; // 40

$_ 存储控制台中最后一个表达式的计算结果。这对于无需重新输入即可链式执行操作非常有用。

技巧 5:copy():即时剪贴板

const data = { users: [...], products: [...] };
copy(data);

无需再右键单击并选择“复制对象”,也无需尝试将文本字符串化并选中。只需copy(anything)点击一下,它就复制到剪贴板了。

技巧 6:getEventListeners() 事件检查器

getEventListeners($0);

这对于调试事件处理器问题或查找未移除的监听器导致的内存泄漏非常有用。

最后

正确的调试技巧可以为你节省数小时的试错时间。

但比这些快捷键更有价值的,是一种「工具思维」的习惯。

当你不再把 DevTools 看作一个静态的调试面板,而是一个可以对话、可以编程、可以无限扩展的工作台时,你的调试方式就会发生质变。

每一次敲击 $0copy(),本质上都是在训练一种能力:用最小的摩擦,把想法变成验证。

真正的效率不在于记住多少快捷键,而在于你是否愿意停下来,花十分钟学习一个能每天为你节省一分钟的工具。

这种「时间复利」的思维方式,才是优秀开发者与普通开发者的分水岭。掌握这些工具,让自己成为那个在控制台里游刃有余、从容应对复杂问题的人。

我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。

欢迎围观我的“网页版朋友圈”,关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。

昨天以前首页

How to Install Google Chrome Web Browser on Ubuntu 24.04

Google Chrome is a fast and secure web browser built for the modern web. It is available for all major operating systems and allows you to sync bookmarks, history, and passwords across devices.

This guide explains how to install Google Chrome on Ubuntu 24.04 using the official Google package and repository.

Info
The official Google Chrome .deb package is available for 64-bit x86 (amd64) systems only. On ARM devices, use Chromium or another ARM-compatible browser.

Quick Reference

Task Command
Download package wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
Install package sudo apt install ./google-chrome-stable_current_amd64.deb
Start Chrome google-chrome
Set as default browser xdg-settings set default-web-browser google-chrome.desktop
Update Chrome sudo apt update && sudo apt upgrade
Uninstall Chrome sudo apt remove google-chrome-stable
Verify repo file cat /etc/apt/sources.list.d/google-chrome.list

Installing Google Chrome on Ubuntu

Chrome is not open source and is not included in the standard Ubuntu repositories. The official .deb package adds the Google signing key and repository so Chrome stays updated automatically.

Download Google Chrome

Open a terminal and use wget to download the latest stable package:

Terminal
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb

Install Google Chrome

Install the package with apt. Running this command requires sudo privileges :

Terminal
sudo apt install ./google-chrome-stable_current_amd64.deb

When prompted, enter your password to complete the installation.

Starting Google Chrome

Open the Activities overview by pressing the Super key, search for “Google Chrome”, and launch it:

Open Google Chrome on Ubuntu 24.04

You can also start Chrome from the terminal:

Terminal
google-chrome

When you start Chrome for the first time, you will be asked whether you want to set it as the default browser and enable crash reports:

Google Chrome default browser prompt on Ubuntu 24.04

Chrome then opens the welcome page:

Google Chrome welcome page on Ubuntu 24.04

From here, you can sign in with your Google account and sync your settings.

Set Chrome as Default Browser

To set Chrome as the default browser from the command line, run:

Terminal
xdg-settings set default-web-browser google-chrome.desktop

To verify the current default browser:

Terminal
xdg-settings get default-web-browser
output
google-chrome.desktop

Updating Google Chrome

During installation, the official Google repository is added to your system. Verify the repository file with cat :

Terminal
cat /etc/apt/sources.list.d/google-chrome.list

Example output:

output
### THIS FILE IS AUTOMATICALLY CONFIGURED ###
# You may comment out this entry, but any other modifications may be lost.
deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main

Chrome updates are delivered through the standard Ubuntu update process:

Terminal
sudo apt update && sudo apt upgrade

Uninstalling Google Chrome

To remove Google Chrome from your system, run:

Terminal
sudo apt remove google-chrome-stable

Then clean up unused dependencies:

Terminal
sudo apt autoremove

Troubleshooting

The installation fails with dependency errors
Fix broken dependencies and re-run the install:

Terminal
sudo apt --fix-broken install
sudo apt install ./google-chrome-stable_current_amd64.deb

Chrome does not start after installation
Close any running Chrome processes and start it again:

Terminal
pkill -f chrome
google-chrome

Repository file is missing
Reinstall the package to recreate the repository file:

Terminal
sudo apt install ./google-chrome-stable_current_amd64.deb

FAQ

What is the difference between Chrome and Chromium?
Chromium is the open-source project that Chrome is built on. Chrome adds proprietary features such as automatic updates, licensed media codecs (AAC, H.264), and tighter Google account integration.

How do I import bookmarks from another browser?
Open Chrome, go to Settings > Import bookmarks and settings, and select the browser you want to import from.

Can I install Chrome Beta or Dev channels?
Yes. Replace google-chrome-stable with google-chrome-beta or google-chrome-unstable in the download URL and install command.

Conclusion

Google Chrome installs on Ubuntu 24.04 using the official .deb package, which also configures the Google repository for automatic updates.

If you have any questions, feel free to leave a comment below.

❌
❌