class Bar { constructor(x) { this.x = x; } getX() { return this.x; } }
function Bar(x) { this.x = x; } Bar.prototype.getX = function getX() { return this.x; };
getX
property to the getX
object. This will work the same as with any other object, since prototypes in JavaScript are the same objects. In prototype programming languages, such as JavaScript, methods are accessed through prototypes, while fields are stored in specific instances.Bar
, which we call foo
. const foo = new Bar(true);
'x'
. The prototype of foo
is Bar.prototype
, which belongs to the class Bar
.Bar.prototype
has the form of itself, containing the only property 'getX'
, whose value is determined by the function 'getX'
, which when called returns this.x
The prototype Bar.prototype
is the Object.prototype
, which is part of the JavaScript language. Object.prototype
is the root of the prototype tree, whereas its prototype is null
.Bar.prototype
object. class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX(); // ^^^^^^^^^^
const x = foo.getX(); // is actually two steps: const $getX = foo.getX; const x = $getX.call(foo);
this
. Let's take a closer look at the first step, in which the getX
method is getX
from the foo
instance.foo
and understands that the form foo
does not have any property 'getX'
, so it has to go through a prototype chain to find it. We get to Bar.prototype
, we look at the prototype form, we see that it has the property 'getX'
at zero offset. We look for the value at this offset in Bar.prototype
and find the JSFunction getX
we were looking for. const foo = new Bar(true); foo.getX(); // → true Object.setPrototypeOf(foo, null); foo.getX(); // → Uncaught TypeError: foo.getX is not a function
foo.getX()
twice, but each time it has completely different meanings and results. That is why, despite the fact that prototypes are just objects in JavaScript, speeding up access to prototype properties is even more important task for JavaScript engines than speeding up access to properties for regular objects. class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX(); // ^^^^^^^^^^
foo
does not contain 'getX'
and it has not changed. This means that no one has changed the foo object by adding or removing a property or changing one of the attributes of a property.Bar.prototype
. So no one changed the prototype of foo
using Object.setPrototypeOf()
or assigning it to a special _proto_
property.Bar.prototype
contains 'getX'
and has not changed. This means that no one has changed Bar.prototype
by adding or removing a property or changing one of the attributes of a property. const anchor = document.createElement('a'); // → HTMLAnchorElement const title = anchor.getAttribute('title');
HTMLAnchorElement
and we call the getAttribute()
method. The chain for this element already includes 6 prototypes! Most of the interesting DOM methods are not in the prototype of HTMLAnchorElement
, but somewhere upstream.getAttribute()
method is in the Element.prototype
. This means that every time we call anchor.getAttribute()
, the JavaScript engine needs to:'getAttribute'
not an anchor
object itself;HTMLAnchorElement.prototype
;'getAttribute'
there;HTMLElement.prototype
;'getAttribute'
;Element.prototype
;'getAttribute'
present.'getX'
for foo
: class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const $getX = foo.getX;
foo
prototype changes, the engine moves to a new form. Now we need to check only the shape of the object to confirm the absence of certain properties, as well as to protect the prototype link (guard the prototype link).ValidityCell
, which is associated with it.ValidityCell
invalidated every time when someone changes the prototype associated with it or any other prototype above it. Let's see how it works.Bar.prototype
), the instance form (in our case, the form foo
), and also binds the current ValidityCell
to the prototype obtained from the form instance (in our case Bar.prototype
is taken).ValidityCell
. If it is still valid, the engine directly uses the offset on the prototype, skipping the extra search steps.ValidityCell
cell is disabled. Because of this, the inline cache is skipped the next time it starts up, resulting in poor performance.Object.prototype
not only invalidates Inline caches for Object.prototype
, but also for any prototype in the chain under it, including EventTarget.prototype
, Node.prototype
, Element.prototype
, etc., to the HTMLAnchorElement.prototype
itself.Object.prototype
for as long as the code runs is a terrible loss of performance. Do not do this!Bar
and a function loadX
, which calls a method on objects of type Bar
. We call the loadX
function several times with instances of the same class. class Bar { /* … */ } function loadX(bar) { return bar.getX(); // IC for 'getX' on `Bar` instances. } loadX(new Bar(true)); loadX(new Bar(false)); // IC in `loadX` now links the `ValidityCell` for // `Bar.prototype`. Object.prototype.newMethod = y => y; // The `ValidityCell` in the `loadX` IC is invalid // now, because `Object.prototype` changed.
loadX
now pointing to ValidityCell
for Bar.prototype
. If you then change (mutate) Object.prototype
, which is the root of all the prototypes in JavaScript, ValidityCell
becomes invalid and existing Inline caches will not be used next time, which leads to poor performance.Object.prototype
is always a bad idea, as it invalidates any inline caches for loaded prototypes at the time of the change. Here is an example of how NOT to do: Object.prototype.foo = function() { /* … */ }; // Run critical code: someObject.foo(); // End of critical code. delete Object.prototype.foo;
Object.prototype
, which invalidates all Inline prototype caches loaded by the engine at this point. Then we run some code that uses the method we described. The engine will have to start from the very beginning and configure inline caches for any access to the prototype property. And then, finally, “tidying up after ourselves” and removing the method of the prototype, which we added earlier.Object.prototype
, so that all Inline caches are again invalidated, and the engine has to start work from the very beginning again.Summarize
We learned how JavaScript stores objects and classes, and how forms, inline caches, and validity cells help optimize operations with prototypes. Based on this knowledge, we realized how to improve performance from a practical point of view: do not touch the prototypes! (or if you really need it, do it before executing the code).
Source: https://habr.com/ru/post/449144/
All Articles