Is Chrome’s JavaScript console lazy about evaluating objects?

JavascriptGoogle ChromeJavascript Objectsconsole.log

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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

Solution 3 - Javascript

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.

Solution 4 - Javascript

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));

Solution 5 - Javascript

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

Solution 6 - Javascript

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);

Solution 7 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEric MickelsenView Question on Stackoverflow
Solution 1 - JavascriptEric MickelsenView Answer on Stackoverflow
Solution 2 - JavascriptnonopolarityView Answer on Stackoverflow
Solution 3 - JavascriptyingtedView Answer on Stackoverflow
Solution 4 - JavascriptjustinsAccountView Answer on Stackoverflow
Solution 5 - JavascriptpticaView Answer on Stackoverflow
Solution 6 - JavascriptwrygielView Answer on Stackoverflow
Solution 7 - JavascriptShadow Wizard Says No More WarView Answer on Stackoverflow