A JS __proto__ “fun” fact

Andrea Giammarchi
3 min readNov 25, 2022

--

Photo by Mark Williams on Unsplash

If you’ve followed me long enough, you already know how much against __proto__ I have always been and yet, since it made it to ECMAScript standard a long while ago, there are still rare occasions I find at least its usage to brand an object acceptable:

const factory = (fields, {prototype} = Object) => ({
__proto__: prototype,
...fields
});

// mimic an <a /> reference
factory({href: '/'}, HTMLAnchorElement);

Beside allowing me to create even non construct-able instances, __proto__ has been a good performance help in Firefox, while it has no optimizations in Chrome or Safari where it scores instead really badly.

In Firefox, __proto__ scores better than new Class

Putting performance aside, and please note I am not suggesting the usage of __proto__ at all in the wild, this post exists for other reasons.

Object.prototype.__proto__

I am pretty sure everyone knows what __proto__ accessor does, so I’d like to propose you a challenge … and yes, this is all valid JS:

class Test {}
const __proto__ = Test.prototype;

// what do these operations return?
({__proto__} instanceof Test);
({__proto__: __proto__} instanceof Test);
({"__proto__": __proto__} instanceof Test);
({["__proto__"]: __proto__} instanceof Test);

Now that your jaw likely dropped, I’d like to propose you a followup challenge:

// what do these operations return?
({__proto__}.__proto__ === Test.prototype);
({__proto__: __proto__}.__proto__ === Test.prototype);
({"__proto__": __proto__}.__proto__ === Test.prototype);
({["__proto__"]: __proto__}.__proto__ === Test.prototype);

What the heck is going on?

If you haven’t run yourself these snippets to know the answer, I can help with that! In the first challenge, the output will be false, true, true and false again, and the reason is that both {__proto__} and {["__proto__"]} are defined as own property, while the assignment via syntax, without using special syntax features, even with quotes around, passes actually through the Object.prototype.__proto__ accessor.

On the other hand, in the second challenge (all true) it doesn’t really matter if we access __proto__ directly or as ["__proto__"], so that basically there is zero symmetry in the way we can trigger the __proto__ setter, from the way we can trigger the __proto__ getter once it’s either an own property or a magic inheritance gate.

… and not only __proto__

I haven’t checked myself but I believe every legacy __xxx__ special accessor suffers the same __proto__ curse, so please be careful out there mixing up latest cool JS syntax to assign properties with your actual intent and expectations, as things might easily go bananas if you try to be smart in assigning these properties.

And that’s all folks!

The only take away from this post is likely summarized as such:

  • do not use legacy-yet-working parts of the language, if you’d like to avoid surprises today, or tomorrow, about how these behave with new syntax
  • prefer explicit (and unfortunately slower) Object.setPrototypeOf whenever manually setting the prototype of an object is strictly required, but do use classes in every other case whenever is possible
  • erase from your memory this post or shrink it into a short sentence like: “I should never use __proto__ in my code” and move along learning fancy new stuff that doesn’t mess up with your brain or feelings

See you soon for the next shenanigan 👋 😜

--

--

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.