web-dev-qa-db-fra.com

La console JavaScript de Chrome est-elle paresseuse quant à l'évaluation des tableaux?

Je vais commencer par le code:

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

C'est simple, non? En réponse à cela, Firebug dit:

["hi"]
["bye"]

Magnifique, mais la console JavaScript de Chrome (7.0.517.41 bêta) dit:

["bye"]
["bye"]

Ai-je fait quelque chose de mal ou la console JavaScript de Chrome est-elle exceptionnellement paresseuse quant à l'évaluation de ma baie?

enter image description here

113
Eric Mickelsen

Merci pour le commentaire, tec. J'ai pu trouver un bogue Webkit non confirmé existant qui explique ce problème: https://bugs.webkit.org/show_bug.cgi?id=35801 (EDIT: maintenant corrigé!)

Il semble y avoir un débat sur la quantité de bogue et s'il peut être corrigé. Cela me semble être un mauvais comportement. Cela m'a particulièrement troublé car, dans Chrome au moins, cela se produit lorsque le code réside dans des scripts qui sont exécutés immédiatement (avant le chargement de la page), même lorsque la console est ouverte, à chaque fois que la page est actualisée. L'appel de console.log lorsque la console n'est pas encore active entraîne uniquement une référence à l'objet mis en file d'attente, et non à la sortie que la console contiendra. Par conséquent, le tableau (ou tout autre objet) ne sera évalué que la console est prête. C'est vraiment une évaluation paresseuse.

Cependant, il existe un moyen simple d'éviter cela dans votre code:

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

En appelant toString, vous créez une représentation en mémoire qui ne sera pas modifiée par les instructions suivantes, que la console lira lorsqu'elle sera prête. La sortie de la console est légèrement différente de la transmission directe de l'objet, mais cela semble acceptable:

hi
bye
58
Eric Mickelsen

D'après l'explication d'Eric, cela est dû à la mise en file d'attente de console.log(), et il imprime une valeur ultérieure du tableau (ou objet).

Il peut y avoir 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
17

Vous pouvez cloner un tableau avec Array#slice:

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

Une fonction que vous pouvez utiliser au lieu de console.log qui n'a pas ce problème est le suivant:

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

Pour le cas des objets, malheureusement, la meilleure méthode semble être de déboguer d'abord avec un navigateur non WebKit, ou d'écrire une fonction compliquée à cloner. Si vous ne travaillez qu'avec des objets simples, où l'ordre des clés n'a pas d'importance et il n'y a pas de fonctions, vous pouvez toujours faire:

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

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

Toutes ces méthodes sont évidemment très lentes, donc encore plus qu'avec la normale console.logs, vous devez les retirer une fois le débogage terminé.

6
yingted

Cela a été corrigé dans Webkit, cependant lorsque vous utilisez le framework React cela se produit pour moi dans certaines circonstances, si vous rencontrez de tels problèmes, utilisez simplement ce que d'autres suggèrent:

console.log(JSON.stringify(the_array));
2
justinsAccount

C'est déjà répondu, mais je laisserai quand même tomber ma réponse. J'ai implémenté un simple wrapper de console qui ne souffre pas de ce problème. Nécessite jQuery.

Il implémente uniquement les méthodes log, warn et error, vous devrez en ajouter d'autres pour qu'il soit interchangeable avec un console normal.

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

On dirait que Chrome remplace dans sa phase de "pré-compilation" toute instance de "s" par pointeur vers le tableau réel.

Une solution consiste à cloner le tableau, en enregistrant une nouvelle copie à la place:

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;
}
0
Shadow Wizard