4

If we create an array in chrome console,

arr=[1,2];

and check the prototype chain in chrome devtools using arr.__proto__.__proto__, we get the following result. enter image description here

As you can see, the __proto__ object is pointing to null, which I was expecting logically. But when I tried to access the same level of the prototype chain, i.e the Object.Prototype, by using arr.__proto__, and then searching through the dropdown menus, I got the following result. enter image description here

As you can see in the highlighted line, this time the __proto__ is pointing to another Object.Prototype. On opening this further, I got the same contents as I got in the previous command(check previous pic).

Can someone please tell me how this extra layer of an Object.prototype is created in some commands, but not in others?

P.S. I'm just learning prototypal inheritance, and this was my attempt to understand how it is implemented in the JS environment.

sayandcode
  • 1,775
  • 1
  • 11
  • 24
  • 1
    Ugh, that's awful. *"I'm just learning prototypal inheritance"* and *"As you can see, the __proto__ object is pointing to null, which I was expecting logically."* You seem to be doing a good job learning! Because you're exactly right. The prototype of `arr` is `Array.prototype`, and its prototype is `Object.prototype`, and *its* prototype is `null`. So nice one! – T.J. Crowder Apr 20 '22 at 08:28

1 Answers1

4

This is due to an odd behavior in Chrome dev tools. The (now deprecated) __proto__ property is a getter/accessor property, which means when it is accessed, it runs a portion of code. That portion of code looks something like so:

Object.getPrototypeOf(this); // which returns this[[Prototype]]

The this in the above example is typically the object that you call .__proto__ on. For example, if you did arr.__proto__, then this would be arr, so we end up getting the prototype of the arr array as expected. In the Chrome dev tools console, things are a little different. Rather than the getter being invoked on an object like arr, it is instead invoked manually when you press (...):

Image showing how an ellipsis icon can be pressed for the proto getter property

So now the question is - what is the value of this when performing Object.getPrototypeOf(this); inside of the __proto__ getter when it is invoked manually in the Chrome dev tools rather than invoking it via property access such as arr.__proto__? This is up to the Chrome dev tools team to decide, but it seems like the way that it behaves is that it sets the this to the originally logged object1. In your second example, that object is arr.__proto__. As a result, the getter ends up showing the prototype of arr.__proto__ again, rather than null.

The below code snippet (see Chrome console output), is a simple example of this behavior in action:

const obj = Object.create({
  get nested() {
    console.log("obj === this:", this === obj); // true
    console.log("obj[[Prototype]] === this:", this === Object.getPrototypeOf(obj)); // false
    return this;
  }
}, {
  foo: {
    value: 'bar',
    enumerable: true
  }
});

// View chrome console for output
console.log(obj);

In the above example, a new object is created with the property {foo: "bar"} which has a prototype set to an object with a getter called nested() {}. This getter returns and logs the value of this. When the above code is run in Chrome, and the getter is invoked by clicking (...) on obj[[Prototype]] (ie: obj.__proto__) nested property, you get the following output:

image to show that the nested getter logs the original object

In the above, the two red boxes represent the same object, showing that this inside of the getter doesn't refer to the prototype object (ie: {nested: (...)}) when it is invoked using (...), but rather it refers to the originally logged object. Unlike this, when you use arr.__proto__.__proto__, the object logged is Object.prototype, so when you invoked the __proto__ getter, the this refers to the object Object.prototype, which returns null when its prototype is accessed.

To properly walk the prototype chain, you can use nested calls to Object.getPrototypeOf():

Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(arr))); // null

1 This is just an assumption based on some observations, but might not be correct - I'm not entirely sure how chrome decides what to set the this to, but the important thing to note is that it isn't always the immediate object that the getter property appears in.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • 1
    This smells a **lot** like a bug in Chrome's devtools. Excellent analysis! Just Yet Another reason not to use `__proto__`. :-) – T.J. Crowder Apr 20 '22 at 08:26
  • 1
    *"To properly walk the prototype chain..."* And to do it in devtools, use the [[Prototype]] link, not the `__proto__` link. – T.J. Crowder Apr 20 '22 at 08:27
  • 1
    Thanks @T.J.Crowder :), it seems like a bug to me too. Good point about using `[[Protoype]]` in the console, that's probably the easiest way to traverse it. – Nick Parsons Apr 20 '22 at 09:12
  • Great Answer! Now that I think about it, that feature of Chrome does make sense. Since the `__proto__` property resides on the Object.prototype, whenever we call it from the command line, it should act on whoever called it, instead of its immediate child. So manually clicking the `__proto__` button nested deep inside the tree, would be exactly the same as it being called by whatever object is on the command line, i.e `arr.__proto__`. That would also explain why this phenomenon cascades until it is called inside the section `__proto__:Object`. Each nested __proto__ must be like its own call. – sayandcode Apr 20 '22 at 09:51