Czy konsola JavaScript przeglądarki Chrome jest leniwa w ocenie tablic?

126

Zacznę od kodu:

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

Proste, prawda? W odpowiedzi na to Firebug mówi:

["hi"]
["bye"]

Cudownie, ale konsola JavaScript Chrome (7.0.517.41 beta) mówi:

["bye"]
["bye"]

Czy zrobiłem coś nie tak, czy też konsola JavaScript Chrome jest wyjątkowo leniwa w ocenie mojej tablicy?

wprowadź opis obrazu tutaj

Eric Mickelsen
źródło
1
Obserwuję to samo zachowanie w Safari - więc prawdopodobnie chodzi o zestaw internetowy. Dość zaskakujące. Nazwałbym to błędem.
Lee
7
Dla mnie wygląda to na błąd. W systemie Linux Opera i Firefox wyświetlają oczekiwany wynik, Chrome i inne przeglądarki oparte na Webkit nie. Możesz zgłosić problem twórcom
tec
2
od marca 2016 roku ten numer już nie istnieje.
kmonsoor
1
Kwiecień 2020, mając ten problem w Chrome. Zmarnowałem 2 godziny na szukanie błędu w moim kodzie, który okazał się błędem w Chrome.
The Fox
1
Warto również zauważyć, że iw podpowiedzi z niebieską ikoną jest napisane „Poniższa wartość została właśnie oszacowana”.
user4642212

Odpowiedzi:

69

Dzięki za komentarz, tec. Udało mi się znaleźć istniejący niepotwierdzony błąd Webkita, który wyjaśnia ten problem: https://bugs.webkit.org/show_bug.cgi?id=35801 (EDYCJA: teraz naprawiona!)

Wydaje się, że trwa dyskusja na temat tego, jak bardzo jest to błąd i czy można go naprawić. Wydaje mi się, że to złe zachowanie. Było to dla mnie szczególnie niepokojące, ponieważ przynajmniej w Chrome występuje, gdy kod znajduje się w skryptach, które są wykonywane natychmiast (przed załadowaniem strony), nawet gdy konsola jest otwarta, za każdym razem, gdy strona jest odświeżana. Wywołanie polecenia console.log, gdy konsola nie jest jeszcze aktywna, powoduje jedynie odniesienie do obiektu znajdującego się w kolejce, a nie wyjście, które będzie zawierać konsola. Dlatego tablica (lub dowolny obiekt) nie będzie oceniana, dopóki konsola nie będzie gotowa. To naprawdę jest przypadek leniwej oceny.

Istnieje jednak prosty sposób, aby tego uniknąć w kodzie:

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

Wywołując toString, tworzysz reprezentację w pamięci, która nie zostanie zmieniona przez następujące instrukcje, które konsola odczyta, gdy będzie gotowa. Wynik konsoli różni się nieco od bezpośredniego przekazywania obiektu, ale wydaje się akceptowalny:

hi
bye
Eric Mickelsen
źródło
1
W rzeczywistości w przypadku tablic asocjacyjnych lub innych obiektów może to stanowić prawdziwy problem, ponieważ toString nie wytwarza niczego wartościowego. Czy ogólnie istnieje łatwe obejście problemu dla obiektów?
Eric Mickelsen
29
JSON.stringify ()
draeton
1
webkit dostał łatkę na to kilka miesięcy temu
antony.trupe
1
zrób to: console.log (JSON.parse (JSON.stringify (s));
Lee Comstock
Chciałem tylko wspomnieć, że w obecnej wersji Chrome konsola jest opóźniona i ponownie wyświetla błędne wartości (lub czy kiedykolwiek było dobrze). Na przykład rejestrowałem tablicę i wyświetlałem najwyższą wartość po zarejestrowaniu, ale pokazywała się ona bez wartości wyskakującej. Twoja sugestia toString () była naprawdę pomocna w dotarciu do miejsca, w którym potrzebowałem zobaczyć wartości.
Nicholas R. Grant
21

Z wyjaśnień Erica wynika, że ​​jest console.log()to kolejka do kolejki i drukuje późniejszą wartość tablicy (lub obiektu).

Może być 5 rozwiązań:

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
brak biegunowości
źródło
7

Możesz sklonować tablicę za pomocą Array#slice:

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

Funkcja, której możesz użyć zamiast niej console.log, nie ma tego problemu, jest następująca:

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

W przypadku obiektów, niestety, najlepszą metodą wydaje się być debugowanie najpierw za pomocą przeglądarki innej niż WebKit lub napisanie skomplikowanej funkcji do sklonowania. Jeśli pracujesz tylko z prostymi obiektami, w których kolejność klawiszy nie ma znaczenia i nie ma żadnych funkcji, zawsze możesz zrobić:

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

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

Wszystkie te metody są oczywiście bardzo wolne, więc nawet bardziej niż w przypadku zwykłych metod console.log, musisz je usunąć po zakończeniu debugowania.

yingted
źródło
2

Zostało to załatane w Webkit, jednak podczas korzystania z frameworka React zdarza się to w niektórych okolicznościach, jeśli masz takie problemy, po prostu użyj, jak sugerują inni:

console.log(JSON.stringify(the_array));
justinsAccount
źródło
2
Potwierdzam. To jest dosłownie najgorsze, gdy próbujesz wylogować się z ReactSyntheticEvents. Nawet a JSON.parse(JSON.stringify(event))nie ma odpowiedniej głębokości / dokładności. Oświadczenia debuggera to jedyne prawdziwe rozwiązanie, które znalazłem, aby uzyskać właściwy wgląd.
CStumph
1

To już odpowiedź, ale i tak odrzucę odpowiedź. Zaimplementowałem prosty wrapper konsoli, który nie cierpi z powodu tego problemu. Wymaga jQuery.

Implementuje tylko log, warna errormetody, trzeba będzie dodać trochę więcej, aby mogła ona być stosowane zamiennie z regularnym 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);
wrygiel
źródło
0

Wygląda na to, że Chrome zastępuje w fazie „prekompilacji” dowolne wystąpienie litery „s” wskaźnikiem do rzeczywistej tablicy.

Jednym ze sposobów jest sklonowanie tablicy i zarejestrowanie nowej kopii:

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;
}
Czarodziej cienia jest dla ciebie ucho
źródło
To dobrze, ale ponieważ jest to płytka kopia, nadal istnieje możliwość bardziej subtelnego problemu. A co z obiektami, które nie są tablicami? (To jest teraz prawdziwy problem.) Nie sądzę, żeby to, co mówisz o „prekompilacji”, było poprawne. Ponadto w kodzie jest błąd: clone [clone.length] powinno być clone [i].
Eric Mickelsen
Bez błędu, wykonałem to i było OK. clone [clone.length] jest dokładnie takie samo jak clone [i], ponieważ tablica zaczyna się od długości 0, podobnie jak iterator pętli „i”. W każdym razie nie jestem pewien, jak będzie się zachowywał ze złożonymi obiektami, ale IMO warto spróbować. Jak powiedziałem, to nie jest rozwiązanie, to sposób na obejście problemu ...
Shadow Wizard is Ear For You
@Shadow Wizard: Słuszna uwaga: clone.length będzie zawsze równa i. Nie będzie działać w przypadku obiektów. Być może jest rozwiązanie z „dla każdego”.
Eric Mickelsen
Obiekty masz na myśli? var s = {param1: "cześć", param2: "jak się masz?" }; jeśli tak, właśnie przetestowałem i kiedy masz s ["param1"] = "pa"; działa dobrze, zgodnie z oczekiwaniami. Czy możesz opublikować przykład „to nie zadziała dla obiektów”? Zobaczę i spróbuję się na to wspiąć.
Shadow Wizard is Ear For You
@Shadow Wizard: Oczywiście twoja funkcja nie będzie klonować właściwości i nie będzie działać na żadnych obiektach bez właściwości length. Błąd webkita dotyczy wszystkich obiektów, nie tylko tablic.
Eric Mickelsen
0

jak dotąd najkrótszym rozwiązaniem jest użycie tablicy lub składni rozproszenia obiektów w celu uzyskania klonu wartości, które mają być zachowane w czasie logowania, tj .:

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

należy jednak uważać, ponieważ robi to płytką kopię, więc wszelkie głęboko zagnieżdżone wartości niebędące prymitywami nie zostaną sklonowane i tym samym wyświetlone w zmodyfikowanym stanie w konsoli

ptica
źródło