Categories
console.log google-chrome javascript javascript-objects

Is Chrome’s JavaScript console lazy about evaluating objects?

159

I’ll start with the code:

var s = ["hi"];
console.log(s);
s[0] = "bye";
console.log(s);

Simple, right? In response to this, the Firefox console says:

[ "hi" ]
[ "bye" ]

Wonderful, but Chrome’s JavaScript console (7.0.517.41 beta) says:

[ "bye" ]
[ "bye" ]

Have I done something wrong, or is Chrome’s JavaScript console being exceptionally lazy about evaluating my array?

Screenshot of the console exhibiting the described behavior.

12

  • 1

    I observe the same behavior in Safari — so it’s probably a webkit thing. Pretty surprising. I’d call it a bug.

    – Lee

    Oct 30, 2010 at 6:04

  • 7

    To me it looks like a bug. On Linux Opera and Firefox display the expected result, Chrome and other Webkit-based browsers do not. You might want to report the issue to the Webkit devs: webkit.org/quality/reporting.html

    – tec

    Oct 30, 2010 at 8:41

  • 2

    as of March 2016, this issue is no more.

    – kmonsoor

    Mar 31, 2016 at 20:03

  • 2

    April 2020, having this issue in Chrome. Wasted 2 hours looking for a bug in my code that turned out to be a bug in Chrome.

    – The Fox

    Apr 25, 2020 at 14:30

  • 1

    Also worth noting that the blue i icon’s tooltip says “Value below was evaluated just now.”.

    Jul 13, 2020 at 2:58

86

Thanks for the comment, tec. I was able to find an existing unconfirmed Webkit bug that explains this issue: https://bugs.webkit.org/show_bug.cgi?id=35801 (EDIT: now fixed!)

There appears to be some debate regarding just how much of a bug it is and whether it’s fixable. It does seem like bad behavior to me. It was especially troubling to me because, in Chrome at least, it occurs when the code resides in scripts that are executed immediately (before the page is loaded), even when the console is open, whenever the page is refreshed. Calling console.log when the console is not yet active only results in a reference to the object being queued, not the output the console will contain. Therefore, the array (or any object), will not be evaluated until the console is ready. It really is a case of lazy evaluation.

However, there is a simple way to avoid this in your code:

var s = ["hi"];
console.log(s.toString());
s[0] = "bye";
console.log(s.toString());

By calling toString, you create a representation in memory that will not be altered by following statements, which the console will read when it is ready. The console output is slightly different from passing the object directly, but it seems acceptable:

hi
bye

5

  • 2

    Actually, with associative arrays or other objects, this could be a real problem, since toString doesn’t produce anything of value. Is there an easy work-around for objects in general?

    Oct 30, 2010 at 19:00

  • 1

    webkit landed a patch for this a few months ago

    Oct 9, 2012 at 4:01

  • 3

    do this: console.log(JSON.parse(JSON.stringify(s));

    Apr 11, 2018 at 9:58

  • 1

    I just wanted to mention that in the current Chrome version the console is delayed and outputting values wrong again (or was it ever right). For instance, I was logging an array and popping the top value after logging it, but it was showing up without the popped value. Your toString() suggestion was really helpful in getting to where I needed to get to see the values.

    Dec 6, 2018 at 0:38

  • Inserting a breakpoint from the code with debugger; is also a great option. (Or manually adding the breakpoint from the Developers Tools if it’s feasible).

    Jan 30, 2020 at 22:36


28

From Eric’s explanation, it is due to console.log() being queued up, and it prints a later value of the array (or object).

There can be 5 solutions:

1. arr.toString()   // not well for [1,[2,3]] as it shows 1,2,3
2. arr.join()       // same as above
3. arr.slice(0)     // a new array is created, but if arr is [1, 2, arr2, 3] 
                    //   and arr2 changes, then later value might be shown
4. arr.concat()     // a new array is created, but same issue as slice(0)
5. JSON.stringify(arr)  // works well as it takes a snapshot of the whole array 
                        //   or object, and the format shows the exact structure

1

  • Any solution that copies a list/object will work. My favourite shallow copy for objects is available since ECMAScript 2018: copy = {...orig}

    – Scar

    Aug 23, 2021 at 9:51

7

You can clone an array with Array#slice:

console.log(s); // ["bye"], i.e. incorrect
console.log(s.slice()); // ["hi"], i.e. correct

A function that you can use instead of console.log that doesn’t have this problem is as follows:

console.logShallowCopy = function () {
    function slicedIfArray(arg) {
        return Array.isArray(arg) ? arg.slice() : arg;
    }

    var argsSnapshot = Array.prototype.map.call(arguments, slicedIfArray);
    return console.log.apply(console, argsSnapshot);
};

For the case of objects, unfortunately, the best method appears to be to debug first with a non-WebKit browser, or to write a complicated function to clone. If you are only working with simple objects, where order of keys doesn’t matter and there are no functions, you could always do:

console.logSanitizedCopy = function () {
    var args = Array.prototype.slice.call(arguments);
    var sanitizedArgs = JSON.parse(JSON.stringify(args));

    return console.log.apply(console, sanitizedArgs);
};

All of these methods are obviously very slow, so even more so than with normal console.logs, you have to strip them off after you’re done debugging.