Hacker News new | past | comments | ask | show | jobs | submit login

Unfortunately that tends to print the object with collapsed values, requiring you to expand the object to actually see any of the values.



You could do console.table({x, y}) if you really want to see them initially expanded


I much prefer it being collapsed. When dealing with large objects, the firehose can be annoying.


Then, one day, after 12 hours chasing a race condition bug, you learn that when you expand objects from console.log, it shows you the current value of properties and not the values at the time of the console log. Because the values are evaluated when you click on the arrow.

Then you quit web development and start a new career.


Rite of passage for every web developer. The moment when you realize this is the moment when the universe makes sense again... after long hours of total confusion.


FWIW, it's not like it could work in any other way.

When you console.log() an object, it just stores a pointer to the object, and decorates it with an interactive label containing some text, so that it looks nice. This is fast to do, and most of the time, it's exactly what you want.

To log a static snapshot with equivalent interactive expansion capability, console.log() would have to do a general deep copy of your object - it would have to walk every pointer in the object and replace the pointee with its own recursive full deep copy, making sure to detect and handle cycles well, and keeping track of every replacement to ensure referential integrity (e.g. if two random objects A and B both have a pointer to the same object C, the copied A' and B' better both point at the same C'). An unlucky console.log() could easily copy half of your heap into the console, and god help you if you logged a DOM element.

(Also, all these copies would not be garbage-collected until you cleared the console.)

An universal deep copy is impractical to implement (notice how nobody seems to ever implement it, at all, in any programming language), and having console.log() do one would be an incredibly powerful and unpredictable footgun. Meanwhile, if you want to log a static snapshot of an object, all you need to do is to write console.log(cloneForLog(object)), where cloneForLog() is a function you wrote that does whatever copying is appropriate in your situation.

I think the only bad thing about console.log() is that this behavior is not taught to people as a core and important aspect of the function. I guess maybe if console.log() was restricted to strings, and something like console.logPresentation() was a separate function for printing objects, people would check the docs first and wouldn't be surprised.


> To log a static snapshot with equivalent interactive expansion capability, console.log() would have to do a general deep copy of your object

Not necessarily, and if it did I don't think it would address the actual problem.

console.log could do all the presentation work upfront, right then and there when you log, and provide a collapsed view of that. This would have to include detecting and cycling handles, as it would have to do for a deep copy as you mentioned. The cost would be wasting cycles on this work even if nobody looks at its result.

But where it gets dangerous is if there's any side effects in following the object tree. If visiting for logging e.g. creates nodes, or changes them, or whatever. Arguably "you get what you ask for", but this and the performance hit for generating log output before anyone really looks at it deeply, are probably the main reason things are as they are.


Technically it's V8/DOM <-> CDP <-> devtools JS -> devtools DOM -> Skia displaylist.

In the current status quo, devtools requests values through CDP as you expand the nodes.

Based on experiences with using Expand recursively on surprisingly short JSON network responses, I get the impression either the CDP I/O or the JS driving it is quite slow. Or the implementation's just accidentally quadratic.

In any case, I get the impression the right solution would be to make V8 execute and retain the deep-copy internally, then forward bits of it over CDP as requested.

Hrm, now I'm curious if the underlying mechanics that power the HeapProfiler could be readily repurposed for this.

The only fundamental issue, which is likely been the central blocker all along, is representing objects that are cyclic; the implementation would be closer to "object snapshot" than "literal deep copy".

(CDP = chrome devtools protocol, ie what gets exposed over --remote-debugging-port.)


Err, handling cycles, not cycling handles. Can't edit anymore, but funny anyway...


Yeah I don't remember how I found about this, but I remember it involved some `console.log(JSON.stringify(...))`. It's tricky to notice.


And then you can't repro the bug anymore because it was related to Mobx observables or whatever and that JSON.stringify suddenly made it work so you just... leave it there...


Fun story explaining this to the PR reviewers though :D


I was just about to mention this, I wasted a solid hour or two on this until I realised what was happening.


As I pointed out in another comment:

  console.log({x, y});
the wrapping object is being created at log time, so its values will never be changed after the fact. That could still happen with the contents of x or y themselves, but then it's no different from the original way (console.log(x, y);)


We can actually just test this. Here's some code:

    const x = {value: 0};
    console.log(x);
    console.log({x});
    x.value = 1;
Running that in the latest Chrome javascript console, we see that the first version prints `{value: 0}` and the second prints `{x: {...}}`. When you expand the second one, it will show `{x: {value: 1}}`.


The really fun part is doing this :)...

Evaluate the expression:

    const x = {value: 0};
    console.log(x);
    console.log({x});
    x.value = 1;
You get this:

    {value: 0}
    {x: {…}}
Expand the first arrow of the `x:`:

    v {x: {…}}
     > x: {value: 1}
Now evaluate:

    x.value = 3
Then expand the second arrow:

    v {x: {…}}
     > x:
         value: 3
Now if you unexpand the arrow, you get 1, but if you expand it you get 3 =)... (Well at least in chrome)


> Now if you unexpand the arrow, you get 1, but if you expand it you get 3 =)...

Moreover, if you expand arrow next to {value: 0}, you will see literally this:

    v {value: 0} [i]
        value: 3


> its values will never be changed after the fact

By this I mean, the x and y in the output will never themselves change value. They may be mutated, but they cannot be reassigned in the printed object. The printed object is exclusively referenced by the console itself, even if the nested objects within it may be referenced elsewhere.

> That could still happen with the contents of x or y themselves, but then it's no different from the original way (console.log(x, y);)

By this I mean exactly what you demonstrated, the point being that it had nothing to do with the original suggestion made by jchw.


yep, but then when I expand (click triangle) the {value: 0} line, it shows "value: 1" on the next line, only to rise the confusion.

To be fair, there is also an "i" in a square, reminding me about this behaviour.


Excuse me?! I just tried this out and I honestly never knew this until now. I can only imagine how much this has caused me trouble in the past.


You are not wrong. Is this a repl or a debugger? Because it seems to be mixture? How is that helpful?!


On Node, console.dir(x, {depth: null}) will print the object fully expanded (even in cases where console.log by itself won't). Pass something other than `null` to `depth` and it'll recurse to that depth, as well.

Doesn't help on browser, though; the options arg to console.dir isn't part of the standard there.


You can console.log(JSON.stringify(x, null, 2)) to pretty-print the object as JSON. It doesn’t look as good, but it’ll expand and show you the value as it was the moment you logged it. (As opposed to the value when you open it in dev tools)


If they're primitives most consoles will show you the values without expansion




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: