0

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: Ok, now I get it all! All the confusing stuff with setting __proto__ is exactly about changing __proto__ reference. It triggers bad things (see link in the first comment). We really should stick with the syntax in the accepted answer. Firstly I thought, that bad things happen because of the empty prototype by itself, lacking some members, needed by javascript engine for optimizations.

Yuriy Dyachkov
  • 439
  • 3
  • 16
  • 1
    The accepted answer to [Why is mutating the prototype of an object bad for performance?](http://stackoverflow.com/questions/23807805/why-is-mutating-the-prototype-of-an-object-bad-for-performance) has some info and quotes that might interest you. – noppa Aug 06 '16 at 14:12

1 Answers1

4

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__.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • But this looks like: C++ is an OOP language, but it is highly discouraged, to create custom classes. Or it's just changing the prototype is a slow operation? (then it's not a problem if always keep it in mind) And the cases when it blows away the optimization of the object, are they definite, can you show any example? Or you mean just the extremely deep prototype chain? – Yuriy Dyachkov Aug 06 '16 at 13:41
  • Ok, looks like I get it, I can use custom prototypes, but it's encouraged to use Object.create(null) and other syntax from the answer. What about optimization - nothing bad will happen if I won't make common programming mistakes (keeping in mind, how the prototype chain lookup works). – Yuriy Dyachkov Aug 06 '16 at 13:55
  • 1
    @user3198882: There are very, very, very, *very* few situations in which you have any need to change the prototype of an *existing* object. Just create the object with the right prototype in the first place, either with `Object.create` or via a constructor function. – T.J. Crowder Aug 06 '16 at 13:57
  • 2
    It's worth noting that even though `Object.setPrototypeOf` might be better than assigning to `__proto__`, the performance implications are the same. Both change the prototype chain of an already existing object. Using `Object.create` or constructor function for inheritance is the way to go (like @T.J.Crowder said). – noppa Aug 06 '16 at 14:04
  • 1
    It at least used to be the case that changing `__proto__` in some JS engines didn't just blow away optimisation for the object, but blew away *all* optimised code. – gsnedders Aug 06 '16 at 14:12