NodeList - as mentioned earlier, this is an interface whose implementation is hidden in the depths of the browser. Therefore, what exactly the length property does and how it works specifically for this collection: recalculates each time it is accessed, or caches a value somewhere, you can find out only by immersing yourself in the source code of a specific browser.
As for the display of viewed objects in the console.
How exactly the console shows is for the most part unregulated. Therefore, in different browsers the output will look different.
If we consider specifically Chrome.
The name displayed opposite to proto can be determined using the property named Symbol.toStringTag
In addition, this property affects the operation of Object.prototype.toString

Now the most interesting thing: for output as an array, the console should think what output is an array.
The easiest way: inherit from the Array class
class MyArr extends Array{ }

But you can do with ordinary objects.
It is not clear what the developers were guided by, but if the object has a splice property, with a value as a function, as well as a length property, the Chrome console starts displaying this object as an array:
var base = { get length(){return 0;} splice(){} }
It will look like this in the console:

and in expanded form:

Now everything is ready to make the object very similar in the output to NodeList
function NL() { this.splice = function() {}; } NL.prototype = { length: 0, // значение свойства по умолчанию push(el) { this[this.length] = el; this.length++; }, [Symbol.toStringTag]: "NL" }; var nl = new NL(); nl.push(10); nl.push(20); console.debug({ a: nl }); console.log(nl + ''); console.log(Object.prototype.toString.call(nl));
When you run the snippet in the Chrome console, you can see the following:

Which is very similar to the picture in question.