112

This bit of code I understand. We make a copy of A and call it C. When A is changed C stays the same

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

But when A is an array we have a different situation. Not only will C change, but it changes before we even touch A

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

Can someone explain what happened in the second example?

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Frederik H
  • 1,406
  • 2
  • 13
  • 15
  • 5
    If you're wondering about the sort seeming to be observable before it happens, make a shallow clone of the Array when logging. You'll see the actual result. `console.log(C.slice()); A.sort(); console.log(C);` Don't put too much trust in `console` representations of data. They're imperfect. –  Jul 01 '12 at 18:58
  • 1
    @FrederikH Actually what you've described is a known bug that was patched for Webkit in August 2012 (not yet pulled into Google Chrome). See my answer for details. – Elliot B. Sep 08 '14 at 20:57
  • See also [Is console.log() async or sync?](http://stackoverflow.com/q/23392111/1048572) – Bergi Sep 13 '15 at 13:01
  • Or take a look at the even more famous [console.log object at current state](http://stackoverflow.com/questions/7389069/console-log-object-at-current-state) – RiZKiT Apr 29 '16 at 12:23
  • Perhaps use console.table() ? – Dan Nov 26 '20 at 09:06

7 Answers7

114

console.log() is passed a reference to the object, so the value in the Console changes as the object changes. To avoid that you can:

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

MDN warns:

Please be warned that if you log objects in the latest versions of Chrome and Firefox what you get logged on the console is a reference to the object, which is not necessarily the 'value' of the object at the moment in time you call console.log(), but it is the value of the object at the moment you open the console.

gre_gor
  • 6,669
  • 9
  • 47
  • 52
Ka Tech
  • 8,937
  • 14
  • 53
  • 78
  • 1
    this works for me. helps a lot with debugging. just curious, does this create a new Object every time i log? Its basically cloning the current state of the object right? I wonder if it will affect in the long run if I forgot to remove these logging functions before going production. – pokken Jun 03 '19 at 06:20
  • @pokken Yes, all this does is create a String copy of your object. I don't see why leaving logging functions could have adverse affects when going into prod – kebab-case Jun 27 '19 at 18:59
  • Add this to index.html at production and it will disable console.log altogether. @pokken – Ka Tech Jul 01 '19 at 03:50
49

Pointy's answer has good information, but it's not the correct answer for this question.

The behavior described by the OP is part of a bug that was first reported in March 2010, patched for Webkit in August 2012, but as of this writing is not yet integrated into Google Chrome. The behavior hinges upon whether or not the console debug window is open or closed at the time the object literal is passed to console.log().

Excerpts from the original bug report (https://bugs.webkit.org/show_bug.cgi?id=35801):

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

1) create an object literal with one or more properties

2) console.log that object but leave it closed (don't expand it in the console)

3) change one of the properties to a new value

now open that console.log and you'll see it has the new value for some reason, even though it's value was different at the time it was generated.

I should point out that if you open it, it will retain the correct value if that wasn't clear.

Response from a Chromium developer:

Comment #2 From Pavel Feldman 2010-03-09 06:33:36 PST

I don't think we are ever going to fix this one. We can't clone object upon dumping it into the console and we also can't listen to the object properties' changes in order to make it always actual.

We should make sure existing behavior is expected though.

Much complaining ensued and eventually it led to a bug fix.

Changelog notes from the patch implemented in August 2012 (http://trac.webkit.org/changeset/125174):

As of today, dumping an object (array) into console will result in objects' properties being read upon console object expansion (i.e. lazily). This means that dumping the same object while mutating it will be hard to debug using the console.

This change starts generating abbreviated previews for objects / arrays at the moment of their logging and passes this information along into the front-end. This only happens when the front-end is already opened, it only works for console.log(), not live console interaction.

Elliot B.
  • 17,060
  • 10
  • 80
  • 101
  • 2
    Despite being 'fixed', this issue is still happening for me, both in Chrome 46.0.2490.86 and also in Qt's WebKit (Qt 5.5). Very confusing when an object's logged values *change* on you. For now I think I may try to avoid the problem by doing a deep copy of the object each time I print it. – Vern Jensen Dec 31 '15 at 21:11
  • 1
    It's fixed in Webkit, but the fix hasn't been pulled into Chrome. Chrome was forked from Webkit roughly around the time the patch was introduced. – Elliot B. Jan 01 '16 at 22:54
  • 2
    So instead of being able to just print the object or array involved, the developer has to find a verbose and boiler-platey way to print the contents of that object or array AT THE TIME OF PRINTING, just because Chrome devs are too stubborn to implement the patch for this? Total insanity! – klaar Dec 15 '16 at 10:10
  • As far as I can tell, this is also an issue in the latest Firefox (53 as of this comment). If the object you're trying to see requires you to click expand to see its properties, the console output will show the updated variable, even if you log it before you make the change. For example: `let test = [{a: 1}, {b: 2}]; console.log(test); test[0].xxx = 100; console.log(test);`. – Galen Long Apr 28 '17 at 15:37
  • 5
    The *as of today* part should actually come first. – Jonas Wilms Aug 19 '18 at 13:02
34

Updated on March 9, 2023

The latest guidance from Mozilla as of March 2023:

Information about an object is lazily retrieved. This means that the log message shows the content of an object at the time when it's first viewed, not when it was logged. For example:

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

This will output {}. However, if you expand the object's details, you will see prop: 123.

If you are going to mutate your object and you want to prevent the logged information from being updated, you can deep-clone the object before logging it. A common way is to JSON.stringify() and then JSON.parse() it:

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

There are other alternatives that work in browsers, such as structuredClone(), which are more effective at cloning different types of objects.

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"]

Previous Guidance from MDN

The latest guidance from Mozilla as of February 2023:

Don't use console.log(obj), use console.log(JSON.parse(JSON.stringify(obj))).

This way you are sure you are seeing the value of obj at the moment you log it. Otherwise, many browsers provide a live view that constantly updates as values change. This may not be what you want.

raychz
  • 1,085
  • 1
  • 8
  • 24
  • 5
    Thanks! Ugh the amount of boilerplate to do basic things in javascript without shooting yourself in the foot... – Adam Hughes Jan 15 '20 at 21:09
  • 2
    Isn't the entire point of logging predicated on the need to save a snapshot of reality at a precise point within the logical flow of a program's execution? Therefore, it is practically nonsense to obliterate these snapshots in favor of an arbitrary "whatever the value was last" once the program ends. – Adam Friedman Oct 07 '21 at 22:04
  • That guidance having been on MDN since 2019 or longer doesn't make it "latest". – Bergi Oct 19 '21 at 20:23
  • No, but it makes it the "latest guidance from Mozilla" as I stated in my answer. – raychz Nov 02 '21 at 18:32
  • Thanks for refering the documentation. – Akhil Dec 06 '21 at 19:12
  • It is Sept 14, 2022 and it's still the same, just as an fyi. This answer works. I wonder when they're going to fix it. Caused me 2 days of wtfudgery! – Bennybear Sep 14 '22 at 18:08
  • 1
    @Bennybear it caused me several days of wtfudgery as well, which is why I make it a point to update this answer monthly, haha – raychz Sep 20 '22 at 23:04
14

Arrays are objects. Variables refer to objects. Thus an assignment in the second case copied the reference (an address) to the array from "A" into "C". After that, both variables refer to the same single object (the array).

Primitive values like numbers are completely copied from one variable to another in simple assignments like yours. The "A++;" statement assigns a new value to "A".

To say it another way: the value of a variable may be either a primitive value (a number, a boolean, null, or a string), or it may be a reference to an object. The case of string primitives is a little weird, because they're more like objects than primitive (scalar) values, but they're immutable so it's OK to pretend they're just like numbers.

Andrew
  • 2,598
  • 16
  • 27
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 2
    So there's no way to print out an array to the console, modify the array, and then print out the modified version? – Nate Jan 05 '14 at 19:14
  • 3
    @Nate yes there is; I'm not really sure what it is in my answer that's confusing. The second example in the original question was probably a side-effect of the delay inherent in the way `console.log` works. In my experience, Chrome's developer console is the most problematic in this regard. – Pointy Jan 05 '14 at 19:24
  • I'm sorry, I misread the original question. I'm having the problem that when I print out an array, remove elements using `splice()`, and then print it out again, the spliced version is printed out both times (even though the first print statement is before the splicing). I should have read the OP's question more carefully. – Nate Jan 05 '14 at 19:34
  • 2
    @Nate OK - in my experience Chrome is the worst about that. I've never found a way to make it behave better, but then I haven't really tried that hard either. – Pointy Jan 05 '14 at 19:36
4

EDIT: Keeping this answer just to preserve useful comments below.

@Esailija is actually right - console.log() will not necessarily log the value the variable had at the time you tried to log it. In your case, both calls to console.log() will log the value of C after sorting.

If you try and execute the code in question as 5 separate statements in the console, you will see the result you expected (first, [2, 1], then [1, 2]).

Dmytro Shevchenko
  • 33,431
  • 6
  • 51
  • 67
  • I don't think that really happens. If it does, it's due to the sometimes weird way that `console.log()` works - sometimes it's not completely synchronous with code execution. – Pointy Jul 01 '12 at 18:44
  • @Pointy then, how do you explain that the order of elements is changed before calling `.sort()`? – Dmytro Shevchenko Jul 01 '12 at 18:45
  • I don't know; I'm going to try it now. *edit* when I try it, it shows that the values in the array are in fact different before and after the sort. In other words, logging C[0] before the sort shows it as being 2, and after the sort C[0] is 1. – Pointy Jul 01 '12 at 18:45
  • 3
    google chrome doesn't log the state of an object at the point it is logged. Run it in ie9 or firefox console. – Esailija Jul 01 '12 at 18:46
  • 1
    See also http://stackoverflow.com/questions/5223513/why-doesnt-console-log-take-a-snapshot-of-the-passed-variables – user123444555621 Jul 01 '12 at 19:03
3

Though it's not going to work in every situation, I ended up using a "break point" to solve this problem:

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'}
benipsen
  • 493
  • 6
  • 12
0

The issue is present in Safari as well. As others have pointed out in this and similar questions, the console is passed a reference to the object, it prints the value of the object at the time the console was opened. If you execute the code in the console directly for example, the values print as expected. Instead of JSON stringifying, I prefer to spread arrays (e.g. in your case console.log([...C]);) and objects: the result is quite the same, but the code looks a bit cleaner. I have two VS code snippets to share.

    "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"
   }

In order to get the same output as with console.log( JSON.parse(JSON.stringify(c))), you can leave out the string part if you wish. Incidentally, the spread syntax often saves time and code.

Danielozzo
  • 76
  • 1
  • 4