user3198882 user3198882 - 3 months ago 11
Javascript Question

Using javascript __proto__ in browser, naturally, as part of implemented algorithms

Consider the following snippet, here javascript prototypical inheritance is used to collect CSS properties from DOM elements.

elem
array contains objects corresponding to DOM nodes, each object have a
style
field, and each
elem[x].style
is also an object. And
elem[x].style
objects repeat DOM tree hierarchy, being connected through
__proto__
. The
rootStyle
variable serves as a root for all
elem[x].style
prototype chains:

var rootStyle = {},
elem = [{}, {}, {}];

rootStyle.__proto__ = null;
rootStyle.fontSize = '14px';

elem[0].style = {};
elem[0].style.__proto__ = rootStyle;
elem[0].style.fontWeight = 'bold';
elem[0].className = 'my-elem-123';

elem[1].style = {};
elem[1].style.__proto__ = rootStyle;
elem[1].style.fontStyle = 'italic';
elem[1].className = 'my-elem-456';

elem[2].style = {};
elem[2].style.__proto__ = elem[1].style;
elem[2].style.textDecoration = 'underline';
elem[2].className = 'my-elem-789';
...
// later in the code
var cssCode = [],
i, len = 3;
for(i = 0; i < len; i++) {
cssCode.push('.' + elem[i].className + '{' + getCssRules(elem[i]) + '}';
}

function getCssRules(elem) {
var result = [],
cssProperty, cssValue;
for(cssProperty in elem.style) {
// No hasOwnProperty check!
// Here (and in for..in loop) happens the lookup magic that I need
cssValue = elem.style[cssProperty];
result.push(cssValue + ':' + cssProperty + ';';
}
return result;
}


And as said here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto, I should not change object's prototype because of performance impacts.

The object key-value lookup internally turns into many operations, but it's acceptable, it's a managed language anyway, and any operation here have an overhead. And if you don't make crazy depth prototype chains, then the speed of prototype chain lookup should be comparable to the speed of any "single step" operation, I mean, close to complexity of O(1). And if my algorythm naturally needs such data structure with exactly such behaviour, it's pain in the butt - to implement my own chain lookup, or some totally different solution, just because "setting
__proto__
is bad".

So, what are the cases or good / bad usage of
__proto__
?

What will happen if I store references to real DOM nodes in
elem
array like this:

elem[0].domNode = document.body.childNodes[0];
elem[1].domNode = document.body.childNodes[1];
elem[2].domNode = elem[1].domNode.childNodes[0];


Is it good, bad or doesn't matter, to link objects with custom prototype chain, and objects with generic prototype chain, does the engine's optimization fail here ?

Added: IE <= 8 is not needed, I'm using AngularJS anyway, and they dropped the support for old IE.

Added: I don't want to change the prototype of existing object, I'm totally agree with the 1st remark in the accepted answer and the 1st comment to the question. I also can't imagine the situation when it is needed to change prototype of some object, either in the middle of any proccessing, or reuse this way, an object received as a result of some proccessing

Answer

So, what are the cases or good / bad usage of __proto__ ?

Leaving aside whether there are good use cases for setting __proto__,1 you have no need to do so in your code. Instead:

Creating the root with no prototype:

rootStyle = Object.create(null);

Creating an object using rootStyle as its prototype:

elem[0].style = Object.create(rootStyle);

Applying that to the beginning of your code without making any other changes:

var rootStyle = Object.create(null),          // **
    elem = [{}, {}, {}];

rootStyle.fontSize = '14px';

elem[0].style = Object.create(rootStyle);     // **
elem[0].style.fontWeight = 'bold';
elem[0].className = 'my-elem-123';

elem[1].style = Object.create(rootStyle);     // **
elem[1].style.fontStyle = 'italic';
elem[1].className = 'my-elem-456';

elem[2].style = Object.create(elem[1].style); // **
elem[2].style.textDecoration = 'underline';
elem[2].className = 'my-elem-789';

What will happen if I store references to real DOM nodes in elem array like this:

elem[0].domNode = document.body.childNodes[0];
elem[1].domNode = document.body.childNodes[1];
elem[2].domNode = elem[1].domNode.childNodes[0];

The nature of the prototype chain on the elem instances is completely irrelevant to the question of what happens if you do the above.

The answer to "what will happen" if you do the above is: Nothing unusual (unless you're dealing with an obsolete version of IE). It's another reference to the element, so if you removed (say) the first child of document.body after doing that, instead of being cleaned up, it would stick around until you cleared your reference to it in elem[0].domNode, but that's no different from when you maintain a reference to any other object (again, if we're not talking about obsolete versions of IE).


1 Three notes on that:

  1. If you think you need to change the prototype on an existing object, step back and look at your overall design. It's extremely unusual to need to do that; you're usually better off from a design perspective creating a new object of the right type and copying over any relevant information. (I've never had to do it in well over a decade of JavaScript programming. If you were to put it in classical OOP terms [C++, Java], it would be like changing an object's type after creating it; e.g., you created a Cat, but now you want to change it into a Dog. It just is vanishingly rare to have a real need to do that.)

  2. As you've seen on MDN, changing the prototype of an object is not what JavaScript engines are optimized for, so as an outlier case it tends to blow away the optimization of the object, slowing property access. Whether that slowing is in any way significant will vary from use case to use case, of course; worrying about it before it's a problem would be premature optimization.

  3. On modern JavaScript engines, if you do have a genuine use-case for changing a prototype, do it via Object.setPrototypeOf rather than assigning to __proto__.

Comments