174

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.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Eric Mickelsen
  • 10,309
  • 2
  • 30
  • 41
  • 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 '10 at 06:04
  • @mplungjan - that's not true. the first line declares a "plain old" array with a single element at index 0. The third line simply assigns a new value to that element. both cases are working with a simple numerically indexed array. – Lee Oct 30 '10 at 06:06
  • if this is a bug, why this bug wasn't found and fixed is beyond my comprehension. – nonopolarity Oct 30 '10 at 06:23
  • 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: http://webkit.org/quality/reporting.html – tec Oct 30 '10 at 08:41
  • DOH - you are of course correct. I was not fully awake – mplungjan Oct 30 '10 at 09:31
  • I found the same issue with Firebug for Firefox. Really disappointing. I suspected a shuffle function was behaving strangely, until I decided to check with jsbin and used `.toString()`. [Here's the jsbin code](http://jsbin.com/azogov/2/edit). In the console counter part, from line 8 onward, the original array looks shuffled too. – Majid Fouladpour Aug 09 '13 at 09:15
  • Man this was driving me crazy. – Ben Liyanage Feb 27 '15 at 21:05
  • See also [console.log() async or sync?](http://stackoverflow.com/q/23392111/1048572) for a generic explanation – Bergi Aug 06 '15 at 20:38
  • 2
    as of March 2016, this issue is no more. – kmonsoor Mar 31 '16 at 20:03
  • 3
    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 '20 at 14:30
  • 1
    Also worth noting that the blue `i` icon’s tooltip says “Value below was evaluated just now.”. – Sebastian Simon Jul 13 '20 at 02:58
  • I solved mine by removing the setTimeout method – Edison Pebojot Mar 24 '21 at 03:44
  • Related: [console.log() shows the changed value of a variable before the value actually changes](https://stackoverflow.com/q/11284663) - it might be a better dupe target. – VLAZ Oct 26 '22 at 05:44
  • I always lose time on this issue to. But I can only find others having this problem, but no always working solution – Radon8472 Jul 18 '23 at 09:11

7 Answers7

95

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
Eric Mickelsen
  • 10,309
  • 2
  • 30
  • 41
  • 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? – Eric Mickelsen Oct 30 '10 at 19:00
  • 1
    webkit landed a patch for this a few months ago – antony.trupe Oct 09 '12 at 04:01
  • 4
    do this: console.log(JSON.parse(JSON.stringify(s)); – Lee Comstock Apr 11 '18 at 09: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. – Nicholas R. Grant Dec 06 '18 at 00: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). – Giorgio Tempesta Jan 30 '20 at 22:36
30

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
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • 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 '21 at 09:51
  • 1
    @Scar worth mentioning you shallow copy would turn the array into an object – Krismu Aug 12 '22 at 08:42
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.

Domenic
  • 110,262
  • 41
  • 219
  • 271
yingted
  • 9,996
  • 4
  • 23
  • 15
5

This has been patched in Webkit, however when using the React framework this happens for me in some circumstances, if you have such problems just use as others suggest:

console.log(JSON.stringify(the_array));
justinsAccount
  • 742
  • 1
  • 10
  • 10
  • 3
    Can confirm. This is literally the worst when trying to log out ReactSyntheticEvents. Even a `JSON.parse(JSON.stringify(event))` doesn't get the right depth/accuracy. Debugger statements are the only real solution I've found to get the correct insight. – CStumph Jul 27 '15 at 23:35
2

Looks like Chrome is replacing in its "pre compile" phase any instance of "s" with pointer to the actual array.

One way around is by cloning the array, logging fresh copy instead:

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

function CloneArray(array)
{
    var clone = new Array();
    for (var i = 0; i < array.length; i++)
        clone[clone.length] = array[i];
    return clone;
}
Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
  • That's good, but because it's a shallow copy, there is still the possibility of a more subtle problem. And what about objects that aren't arrays? (Those are the real problem now.) I don't think that what you're saying about "pre compile" is accurate. Also, there is an error in the code: clone[clone.length] should be clone[i]. – Eric Mickelsen Oct 31 '10 at 16:54
  • No error, I've executed it and it was OK. clone[clone.length] is exactly like clone[i], as the array start with length of 0, and so does the loop iterator "i". Anyway, not sure how it will behave with complex objects but IMO it's worth a try. Like I said, that's not a solution, it's a way around the problem.. – Shadow The GPT Wizard Nov 01 '10 at 10:17
  • @Shadow Wizard: Good point: clone.length will always be equal to i. It won't work for objects. Perhaps there is a solution with "for each". – Eric Mickelsen Nov 01 '10 at 16:57
  • Objects you mean this? var s = { param1: "hi", param2: "how are you?" }; if so I just tested and when you have s["param1"] = "bye"; it's working fine as expected. Can you please post example of "it won't work for objects"? I'll see and try to climb that one as well. – Shadow The GPT Wizard Nov 02 '10 at 10:27
  • @Shadow Wizard: Obviously, your function will fail to clone properties and will not work on any objects without a length property. The webkit bug affects all objects, not just arrays. – Eric Mickelsen Nov 03 '10 at 14:19
  • @Domenic Because I wasn't familiar with `slice` back then. – Shadow The GPT Wizard Jun 30 '11 at 21:41
  • @Shadow Wizard Fair enough :). I took over Anonymous's answer with a `slice`-based solution. – Domenic Jul 01 '11 at 01:31
  • @Dom why not new Answer then? :) – Shadow The GPT Wizard Jul 01 '11 at 09:17
  • Well, it was already `slice`, but it needed some ESL and clarity fixes. The main content remains Mr. Mysterious Anonymous's so I thought I'd do an edit instead. – Domenic Jul 01 '11 at 12:08
2

the shortest solution so far is to use array or object spread syntax to get a clone of values to be preserved as in time of logging, ie:

console.log({...myObject});
console.log([...myArray]);

however be warned as it does a shallow copy, so any deep nested non-primitive values will not be cloned and thus shown in their modified state in the console

ptica
  • 749
  • 8
  • 16
1

This is already answered, but I'll drop my answer anyway. I implemented a simple console wrapper which doesn't suffer from this issue. Requires jQuery.

It implements only log, warn and error methods, you will have to add some more in order for it to be interchangeable with a regular console.

var fixedConsole;
(function($) {
    var _freezeOne = function(arg) {
        if (typeof arg === 'object') {
            return $.extend(true, {}, arg);
        } else {
            return arg;
        }
    };
    var _freezeAll = function(args) {
        var frozen = [];
        for (var i=0; i<args.length; i++) {
            frozen.push(_freezeOne(args[i]));
        }
        return frozen;
    };
    fixedConsole = {
        log: function() { console.log.apply(console, _freezeAll(arguments)); },
        warn: function() { console.warn.apply(console, _freezeAll(arguments)); },
        error: function() { console.error.apply(console, _freezeAll(arguments)); }
    };
})(jQuery);
wrygiel
  • 5,080
  • 3
  • 24
  • 29