Неужели консоль JavaScript в Chrome ленилась оценивать массивы?
Я начну с кода:
var s = ["hi"];
console.log(s);
s[0] = "bye";
console.log(s);
Просто, правда? В ответ на это Firebug говорит:
["hi"]
["bye"]
Замечательно, но консоль Chrome JavaScript (7.0.517.41 бета) говорит:
["bye"]
["bye"]
Я сделал что-то не так, или консоль JavaScript в Chrome исключительно ленива при оценке моего массива?
6 ответов
Спасибо за комментарий, тек. Мне удалось найти существующую неподтвержденную ошибку Webkit, которая объясняет эту проблему: https://bugs.webkit.org/show_bug.cgi?id=35801 (ПРАВКА: теперь исправлена!)
Похоже, что есть некоторые споры относительно того, сколько это ошибок, и можно ли их исправить. Это кажется плохим поведением для меня. Это меня особенно беспокоило, потому что, по крайней мере в Chrome, это происходит, когда код находится в сценариях, которые выполняются немедленно (до загрузки страницы), даже когда консоль открыта, когда страница обновляется. Вызов console.log, когда консоль еще не активна, приводит только к ссылке на объект в очереди, а не к выводу, который будет содержать консоль. Следовательно, массив (или любой объект) не будет оцениваться, пока консоль не будет готова. Это действительно ленивая оценка.
Однако в вашем коде есть простой способ избежать этого:
var s = ["hi"];
console.log(s.toString());
s[0] = "bye";
console.log(s.toString());
Вызывая toString, вы создаете представление в памяти, которое не будет изменено следующими операторами, которые консоль будет читать, когда будет готова. Вывод консоли немного отличается от прямой передачи объекта, но кажется приемлемым:
hi
bye
По объяснению Эрика, это связано с 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
Вы можете клонировать массив с 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, вы должны удалить их после завершения отладки.
Это было исправлено в Webkit, однако при использовании инфраструктуры React это происходит для меня в некоторых обстоятельствах, если у вас есть такие проблемы, просто используйте, как другие предлагают:
console.log(JSON.stringify(the_array));
На данный момент самым коротким решением является использование синтаксиса распространения массива или объекта, чтобы получить клон значений, которые будут сохранены во время регистрации, то есть:
console.log({...myObject});
console.log([...myArray]);
однако имейте в виду, что это неглубокая копия, поэтому любые глубоко вложенные непримитивные значения не будут клонированы и, таким образом, показаны в их измененном состоянии в консоли
Похоже, что 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;
}
На это уже есть ответ, но я все равно оставлю свой ответ. Я реализовал простую консольную оболочку, которая не страдает от этой проблемы. Требуется 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);
Работа console.log не стандартизирована, поэтому в некоторых случаях некоторые браузеры задерживают console.log по соображениям производительности.
Однако странно, что это происходит для чего-то такого простого.