Консоль JavaScript в Chrome ленив при оценке массивов?


128

Начну с кода:

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

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

["hi"]
["bye"]

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

["bye"]
["bye"]

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

введите описание изображения здесь


1
Я наблюдаю такое же поведение в Safari - так что, вероятно, это вещь webkit. Довольно удивительно. Я бы назвал это ошибкой.
Ли

7
Для меня это похоже на ошибку. В Linux Opera и Firefox показывают ожидаемый результат, а в Chrome и других браузерах на основе Webkit - нет. Вы можете сообщить о проблеме разработчикам Webkit: webkit.org/quality/reporting.html
tec,

2
по состоянию на март 2016 года этого выпуска больше нет.
kmonsoor

1
Апрель 2020 г., возникла эта проблема в Chrome. Потратил 2 часа на поиск ошибки в моем коде, которая оказалась ошибкой в ​​Chrome.
The Fox

1
Также стоит отметить, что iвсплывающая подсказка синего значка говорит: «Значение , указанное ниже, было оценено только что».
user4642212

Ответы:


70

Спасибо за комментарий, tec. Мне удалось найти существующую неподтвержденную ошибку 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

1
На самом деле, с ассоциативными массивами или другими объектами это может быть реальной проблемой, поскольку toString не производит ничего ценного. Есть ли простой способ обхода объектов в целом?
Эрик Микельсен,

29
JSON.stringify ()
draeton

1
webkit выпустил патч для этого несколько месяцев назад
antony.trupe

1
сделайте это: console.log (JSON.parse (JSON.stringify (s));
Ли Комсток

Я просто хотел упомянуть, что в текущей версии Chrome консоль задерживается и снова выводит неверные значения (или это было когда-либо правильно). Например, я регистрировал массив и выскакивал верхнее значение после регистрации, но он отображался без всплывающего значения. Ваше предложение toString () действительно помогло мне добраться туда, где мне нужно было увидеть значения.
Николас Р. Грант

21

По объяснению Эрика, он находится в 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

7

Вы можете клонировать массив с помощью 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.logs, вы должны удалить их после завершения отладки.


2

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

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

2
Могу подтвердить. Это буквально худшее при попытке выйти из ReactSyntheticEvents. Даже у a JSON.parse(JSON.stringify(event))не правильная глубина / точность. Операторы отладчика - единственное реальное решение, которое я нашел, чтобы получить правильное представление.
CStumph

1

На это уже есть ответ, но я все равно оставлю свой ответ. Я реализовал простую консольную оболочку, которая не страдает от этой проблемы. Требуется 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);

0

Похоже, Chrome заменяет на этапе «предварительной компиляции» любой экземпляр «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;
}

Это хорошо, но, поскольку это неглубокая копия, все же существует возможность более тонкой проблемы. А как насчет объектов, не являющихся массивами? (Сейчас это настоящая проблема.) Я не думаю, что то, что вы говорите о «предварительной компиляции», верно. Также в коде есть ошибка: clone [clone.length] должен быть clone [i].
Эрик Микельсен,

Никакой ошибки, я выполнил его, и все было в порядке. clone [clone.length] точно так же, как clone [i], поскольку массив начинается с длины 0, как и итератор цикла «i». Во всяком случае, не уверен, как он будет вести себя со сложными объектами, но ИМО стоит попробовать. Как я уже сказал, это не решение, это способ обойти проблему ..
Shadow Wizard is Ear For You

@Shadow Wizard: Хороший момент: clone.length всегда будет равняться i. Для объектов это не сработает. Возможно, есть решение с «для каждого».
Эрик Микельсен, 01

Объекты ты это имеешь ввиду? var s = {param1: "привет", param2: "как дела?" }; если да, то я только что проверил, и когда у вас s ["param1"] = "bye"; он работает нормально, как и ожидалось. Не могли бы вы опубликовать пример "для объектов не работает"? Я посмотрю и попробую залезть и на эту.
Shadow Wizard is Ear For You

@Shadow Wizard: очевидно, ваша функция не сможет клонировать свойства и не будет работать с любыми объектами без свойства length. Ошибка webkit затрагивает все объекты, а не только массивы.
Эрик Микельсен

0

на данный момент самым коротким решением является использование синтаксиса распределения массива или объекта, чтобы получить клон значений, которые будут сохранены во время регистрации, то есть:

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

однако имейте в виду, что это неглубокая копия, поэтому любые глубоко вложенные непримитивные значения не будут клонированы и, таким образом, показаны в их измененном состоянии в консоли

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.