Является ли консоль Chrome JavaScript ленивой относительно оценки массивов?

Я начну с кода:

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

Просто, правда? В ответ на это Firebug говорит:

["hi"]
["bye"]

Замечательно, но консоль Chrome JavaScript (7.0.517.41 бета) говорит:

["bye"]
["bye"]

Я сделал что-то не так, или консоль Chrome JavaScript исключительно ленива при оценке моего массива?

enter image description here

Ответ 1

Спасибо за комментарий, т.е. Мне удалось найти существующую неподтвержденную ошибку Webkit, которая объясняет эту проблему: https://bugs.webkit.org/show_bug.cgi?id=35801 (EDIT: теперь исправлено!)

Похоже, что есть некоторые дебаты относительно того, какая часть ошибок и исправлена ​​ли она. Мне кажется, что это плохое поведение. Это особенно беспокоило меня, потому что, по крайней мере, в Chrome это происходит, когда код находится в сценариях, которые выполняются немедленно (до загрузки страницы), даже когда консоль открыта, всякий раз, когда страница обновляется. Вызов console.log, когда консоль еще не активна, приводит только к ссылке на объект, находящийся в очереди, а не на вывод, который будет содержать консоль. Поэтому массив (или любой объект) не будет оцениваться до тех пор, пока консоль не будет готова. Это действительно случай ленивой оценки.

Однако есть простой способ избежать этого в коде:

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

Вызывая toString, вы создаете представление в памяти, которое не будет изменено с помощью следующих операторов, которые консоль будет читать, когда она будет готова. Вывод консоли немного отличается от непосредственного прохождения объекта, но представляется приемлемым:

hi
bye

Ответ 2

Из объяснения Эрика это связано с тем, что console.log() находится в очереди, и он печатает более позднее значение массива (или объекта).

Могут быть 5 решений:

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

Ответ 3

Вы можете клонировать массив с помощью Array#slice:

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

Функция, которую вы можете использовать вместо console.log, которая не имеет этой проблемы, выглядит следующим образом:

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

В случае с объектами, к сожалению, лучшим методом является отладка с использованием браузера, отличного от WebKit, или для записи сложной функции для клонирования. Если вы работаете только с простыми объектами, где порядок ключей не имеет значения, а функций нет, вы всегда можете:

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

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

Все эти методы, очевидно, очень медленные, поэтому даже больше, чем с обычными console.log s, вы должны отключить их после завершения отладки.

Ответ 4

Это было исправлено в Webkit, однако при использовании React Framework это происходит для меня в некоторых случаях, если у вас есть такие проблемы, просто используйте, как предлагают другие:

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

Ответ 5

Это уже ответили, но я все равно откажусь от ответа. Я реализовал простую консольную оболочку, которая не страдает от этой проблемы. Требуется jQuery.

Он реализует только методы log, warn и error, вам нужно будет добавить еще немного, чтобы оно было взаимозаменяемым с регулярным 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);

Ответ 6

Похоже, что Chrome заменяет фазу "pre compile" на любой экземпляр "s" указателем на фактический массив.

Один из способов: клонирование массива, запись вместо него следующей копии:

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

Ответ 7

Работа console.log не стандартизирована, поэтому в некоторых случаях некоторые браузеры задерживают console.log по соображениям производительности.

Однако странно, что это происходит для чего-то такого простого.