Чи ленача консоль JavaScript Chrome щодо оцінювання масивів?


126

Почну з коду:

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 - так це, мабуть, річ із веб-кайтів. Досить дивно. Я б назвав це помилкою.
Лі

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

2
станом на березень 2016 року цього питання більше немає.
kmonsoor

1
Квітень 2020 року, випустивши цю проблему в Chrome. Витратили дві години на пошуки помилки в моєму коді, яка виявилася помилкою в Chrome.
Лисиця

1
Також варто зазначити, що iпідказка синього значка говорить про те, що "Значення нижче було оцінено лише зараз".
user4642212

Відповіді:


69

Дякую за коментар, 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.log, вам доведеться позбавити їх після того, як ви закінчите налагодження.


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.length] повинен бути clone [i].
Ерік Мікельсен

Немає помилок, я це виконав, і все було нормально. clone [clone.length] точно такий же, як clone [i], оскільки масив починається з довжини 0, і так ітератор циклу "i". У будь-якому разі, не впевнений, як він поводитиметься зі складними об'єктами, але IMO варто спробувати. Як я вже говорив, це не рішення, це спосіб вирішення проблеми ..
Shadow Wizard is Ear for You

@Shadow Wizard: Добрий момент: clone.length завжди буде рівним i. Він не працюватиме для об’єктів. Можливо, є рішення з "для кожного".
Ерік Мікельсен

Об'єкти ви це маєте на увазі? var s = {param1: "привіт", param2: "як справи?" }; якщо так, я щойно перевірив, і коли у вас s ["param1"] = "bye"; це працює нормально, як очікувалося. Чи можете ви опублікувати приклад "це не працюватиме для об'єктів"? Я побачу і спробую піднятися і на той.
Shadow Wizard - це вухо для вас

@Shadow Wizard: Очевидно, що ваша функція не зможе клонувати властивості і не працюватиме на будь-яких об'єктах без властивості довжини. Помилка webkit впливає на всі об'єкти, а не лише масиви.
Ерік Мікельсен

0

найкоротше рішення поки що - використовувати синтаксис масиву або синтаксис розповсюдження об'єкта, щоб отримати клон значень, який зберігається як під час реєстрації, тобто:

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

однак попередити, як це робиться неглибока копія, тому будь-які глибоко вкладені непомітивні значення не будуть клоновані і, таким чином, відображатимуться у зміненому стані у консолі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.