A JS __proto__ “fun” fact
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.
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 👋 😜