console.log() 在变量实际更改之前显示变量的更改值

console.log() shows the changed value of a variable before the value actually changes

提问人:Frederik H 提问时间:7/2/2012 最后编辑:danronmoonFrederik H 更新时间:4/13/2023 访问量:67407

问:

这段代码我明白了。我们复制了 A 并称之为 C。当 A 改变时,C 保持不变

var A = 1;
var C = A;
console.log(C); // 1
A++;
console.log(C); // 1

但是当 A 是一个数组时,我们的情况就不同了。C 不仅会改变,而且在我们接触 A 之前它就会改变

var A = [2, 1];
var C = A;
console.log(C); // [1, 2]
A.sort();
console.log(C); // [1, 2]

有人可以解释第二个例子中发生了什么吗?

javascript google-chrome 变量

评论

5赞 7/2/2012
如果您想知道排序在发生之前似乎是可观察的,请在日志记录时对 Array 进行浅层克隆。您将看到实际结果。 不要过分信任数据的表示形式。他们并不完美。console.log(C.slice()); A.sort(); console.log(C);console
1赞 Elliot B. 9/9/2014
@FrederikH 实际上,您描述的是一个已知的错误,该错误在 2012 年 8 月为 Webkit 进行了修补(尚未拉入 Google Chrome)。有关详细信息,请参阅我的答案。
0赞 Bergi 9/13/2015
另请参阅 console.log() 是异步还是同步?
0赞 RiZKiT 4/29/2016
或者看看当前状态下更著名的 console.log 对象
0赞 Danny 11/26/2020
也许使用 console.table() ?

答:

14赞 Pointy 7/2/2012 #1

数组是对象。变量是指对象。因此,在第二种情况下,赋值将对数组的引用(地址)从“A”复制到“C”。之后,两个变量都引用同一个对象(数组)。

像数字这样的原始值在像您这样的简单赋值中从一个变量完全复制到另一个变量。“A++;” 语句为“A”分配一个新值。

换一种说法:变量的值可以是原始值(数字、布尔值或字符串),也可以是对对象的引用。字符串基元的情况有点奇怪,因为它们更像是对象而不是基元(标量)值,但它们是不可变的,所以可以假装它们就像数字一样。null

评论

2赞 Nate 1/6/2014
那么没有办法将数组打印出来到控制台,修改数组,然后打印出修改后的版本?
3赞 Pointy 1/6/2014
@Nate是的;我不太确定我的答案中是什么令人困惑。原始问题中的第二个例子可能是工作方式中固有的延迟的副作用。根据我的经验,Chrome 的开发者控制台在这方面是最成问题的。console.log
0赞 Nate 1/6/2014
对不起,我误读了原来的问题。我遇到的问题是,当我打印出一个数组,使用 删除元素,然后再次打印出来时,拼接版本被打印出两次(即使第一个 print 语句在拼接之前)。我应该更仔细地阅读 OP 的问题。splice()
2赞 Pointy 1/6/2014
@Nate好吧 - 根据我的经验,Chrome 是最糟糕的。我从来没有找到让它表现得更好的方法,但我也没有真正努力过。
4赞 Dmytro Shevchenko 7/2/2012 #2

编辑:保留此答案只是为了在下面保留有用的评论。

@Esailija实际上是正确的 - 不一定会记录变量在您尝试记录它时的值。在您的例子中,两个调用都将记录排序的值。console.log()console.log()C

如果您尝试在控制台中以 5 个单独的语句的形式执行相关代码,您将看到预期的结果(首先是、 ,然后是 )。[2, 1][1, 2]

评论

0赞 Pointy 7/2/2012
我不认为这真的会发生。如果是这样,那是由于有时奇怪的工作方式 - 有时它与代码执行不完全同步。console.log()
0赞 Dmytro Shevchenko 7/2/2012
@Pointy那么,您如何解释元素的顺序在调用之前发生了变化?.sort()
0赞 Pointy 7/2/2012
我不知道;我现在要试试。当我尝试时编辑,它显示数组中的值实际上在排序前后不同。换言之,在排序之前记录 C[0] 显示为 2,在排序后记录 C[0] 显示为 1。
3赞 Esailija 7/2/2012
Google Chrome 不会在记录对象时记录对象的状态。在 ie9 或 firefox 控制台中运行它。
1赞 user123444555621 7/2/2012
另请参阅 stackoverflow.com/questions/5223513/...
3赞 benipsen 8/26/2012 #3

虽然它并非在所有情况下都有效,但我最终使用了一个“断点”来解决这个问题:

mysterious = {property:'started'}

// prints the value set below later ?
console.log(mysterious)

// break,  console above prints the first value, as god intended
throw new Error()

// later
mysterious = {property:'changed', extended:'prop'}

评论

0赞 Nico Haase 2/9/2022
请分享更多细节。您将如何在生产代码中使用这个“断点”?
0赞 Nabeel Khan 2/10/2023
@NicoHaase请分享一下为什么要在生产代码中使用“控制台日志”?
0赞 Nico Haase 2/10/2023
@NabeelKhan是什么让你认为我会这样做?
0赞 Nabeel Khan 2/10/2023
@NicoHaase是什么让您认为 op 将在生产中使用控制台.log(从而引发新错误)?
0赞 Nico Haase 2/10/2023
没有什么能让我这么想——我没有写过那个答案,我也不会在生产中使用。你为什么问我这些问题?console.log
49赞 Elliot B. 6/12/2014 #4

Pointy 的回答有很好的信息,但这不是这个问题的正确答案。

OP 描述的行为是 2010 年 3 月首次报告的错误的一部分,该错误于 2012 年 8 月针对 Webkit 进行了修补,但截至撰写本文时,尚未集成到 Google Chrome 中。该行为取决于在将对象文本传递给 时控制台调试窗口是打开还是关闭console.log()

原始错误报告摘录(https://bugs.webkit.org/show_bug.cgi?id=35801):

描述 From mitch kramer 2010-03-05 11:37:45 PST

1) 创建具有一个或多个属性的对象文字

2) console.log 该对象,但将其关闭(不要在控制台中展开它)

3) 将其中一个属性更改为新值

现在打开该控制台.log,您会看到它出于某种原因具有新值,即使它在生成时的值不同。

我应该指出,如果你打开它,如果不清楚,它将保留正确的值。

来自 Chromium 开发人员的回复:

评论 #2 来自 Pavel Feldman 2010-03-09 06:33:36 PST

我认为我们永远不会解决这个问题。我们无法在将对象转储到控制台时克隆对象,也无法侦听对象属性的更改以使其始终实际。

不过,我们应该确保现有行为是预期的。

随之而来的是很多抱怨,最终导致了错误修复。

2012 年 8 月实施的补丁的更新日志说明 (http://trac.webkit.org/changeset/125174):

从今天开始,将对象(数组)转储到控制台将导致对象的属性 在控制台对象扩展时读取(即延迟)。这意味着转储相同的对象,而 使用控制台进行调试将很难对其进行更改。

此更改开始为对象/数组生成缩写预览,此时 日志记录并将此信息传递到前端。这仅在前端发生 ,它仅适用于控制台.log,不适用于实时控制台交互。

评论

2赞 Vern Jensen 1/1/2016
尽管已“修复”,但这个问题仍然发生在我身上,无论是在Chrome 46.0.2490.86中还是在Qt的WebKit(Qt 5.5)中。当对象的记录值在您身上发生变化时,非常令人困惑。现在,我想我可以通过在每次打印对象时对对象进行深度复制来尝试避免这个问题。
1赞 Elliot B. 1/2/2016
它已在 Webkit 中修复,但尚未将修复程序提取到 Chrome 中。Chrome 大约在补丁推出时从 Webkit 分叉出来。
2赞 klaar 12/15/2016
因此,开发人员不能只打印所涉及的对象或数组,而是必须找到一种冗长且样板的方式来打印该对象或数组的内容 在打印时,仅仅因为 Chrome 开发人员太固执而无法为此实现补丁?完全疯狂!
0赞 Galen Long 4/28/2017
据我所知,这也是最新Firefox(截至本评论为53)中的一个问题。如果您尝试查看的对象要求您单击展开以查看其属性,则控制台输出将显示更新的变量,即使您在进行更改之前将其记录下来也是如此。例如:。let test = [{a: 1}, {b: 2}]; console.log(test); test[0].xxx = 100; console.log(test);
5赞 Jonas Wilms 8/19/2018
截至今天,部分实际上应该放在第一位。
115赞 Ka Tech 2/25/2018 #5

console.log()传递对对象的引用,因此控制台中的值会随着对象的更改而变化。为避免这种情况,您可以:

console.log(JSON.parse(JSON.stringify(c)))

MDN 警告

请注意,如果您在最新版本的 Chrome 和 Firefox 中记录对象,则您在控制台上记录的是对对象的引用,这不一定是您调用时对象的“值”,但它是您打开控制台时对象的值。console.log()

评论

1赞 pokken 6/3/2019
这对我有用。对调试有很大帮助。只是好奇,每次我登录时都会创建一个新对象吗?它基本上克隆了对象的当前状态,对吧?我想知道如果我忘记在投入生产之前删除这些日志记录功能,从长远来看是否会受到影响。
0赞 kebab-case 6/28/2019
@pokken 是的,这样做只是创建对象的 String 副本。我不明白为什么在进入生产时离开日志记录功能会产生不利影响
0赞 Ka Tech 7/1/2019
<script> console.log = function () { };</script>在生产环境中将其添加到索引.html中,它将完全禁用控制台.log。@pokken
0赞 Danielozzo 10/13/2019 #6

Safari 中也存在此问题。正如其他人在此问题和类似问题中指出的那样,控制台被传递到对对象的引用,它打印打开控制台时对象的值。例如,如果直接在控制台中执行代码,则按预期打印值。 我更喜欢传播数组,而不是 JSON 字符串化(例如,在您的案例中 console.log([...C]);)和对象:结果完全相同,但代码看起来更简洁一些。我有两个 VS 代码片段要分享。

    "Print object value to console": {
      "prefix": "clo",
      "body": [
         "console.log(\"Spread object: \", {...$0});"
      ],
      "description": "Prints object value instead of reference to console, to avoid console.log async update"
   },
   "Print array value to console": {
      "prefix": "cla",
      "body": [
         "console.log(\"Spread array: \", [...$0]);"
      ],
      "description": "Prints array value instead of reference to console, to avoid console.log async update"
   }

为了获得与 console.log( JSON.parse(JSON.stringify(c))) 相同的输出,您可以根据需要省略字符串部分。顺便说一句,扩展语法通常可以节省时间和代码。

35赞 raychz 11/16/2019 #7

截至2023年3月9日的信息

Mozilla 截至 2023 年 3 月的最新指南:

延迟检索有关对象的信息。这意味着日志消息显示对象在首次查看时的内容,而不是在记录对象时的内容。例如:

const obj = {};
console.log(obj);
obj.prop = 123;

这将输出 .但是,如果展开对象的详细信息,您将看到 .{}prop: 123

如果要更改对象,并且希望阻止更新记录的信息,则可以在记录对象之前对其进行深度克隆。一种常见的方法是,然后:JSON.stringify()JSON.parse()

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

还有其他在浏览器中起作用的替代方法,例如 structuredClone(),它们在克隆不同类型的对象时更有效。

const mushrooms1 = {
  amanita: ["muscaria", "virosa"],
};

const mushrooms2 = structuredClone(mushrooms1);

mushrooms2.amanita.push("pantherina");
mushrooms1.amanita.pop();

console.log(mushrooms2.amanita); // ["muscaria", "virosa", "pantherina"]
console.log(mushrooms1.amanita); // ["muscaria"]

MDN 的上一页指导

Mozilla 截至 2023 年 2 月的最新指南:

不要使用,使用.console.log(obj)console.log(JSON.parse(JSON.stringify(obj)))

这样,您就可以确定在记录它的那一刻看到了它的价值。否则,许多浏览器会提供实时视图,该视图会随着值的变化而不断更新。这可能不是你想要的。obj

评论

5赞 Adam Hughes 1/16/2020
谢谢!呃,在 javascript 中做基本事情的样板数量,而不会搬起石头砸自己的脚......
2赞 Adam Friedman 10/8/2021
难道日志记录的全部意义不在于需要将现实快照保存在程序执行逻辑流中的某个精确点上吗?因此,一旦程序结束,抹去这些快照以支持任意的“无论最后的值是什么”,这实际上是无稽之谈。
0赞 Bergi 10/20/2021
自 2019 年以来,MDN 上的指导或更长时间并不能使它成为“最新”的。
0赞 raychz 11/3/2021
不,但它使它成为“Mozilla的最新指导”,正如我在回答中所说的那样。
1赞 raychz 9/21/2022
@Bennybear这也给我带来了几天的烦恼,这就是为什么我强调每月更新这个答案,哈哈