1

If I add a new property to the prototype of HTMLElement, and have its default value to be '{}' (an empty object):

Object.defineProperty(HTMLElement.prototype, 'customObject', 
{ configurable: true, enumerable: true, writeable: true, value: {} });

Now I create a new div (which is also an HTMLElement):

var e = document.createElement('div');

I assign a property to the customObject of e:

e.customObject.prop = "bla";

If I alert it, I see the desired value:

alert(e.customObject.prop); // Displays 'bla'.

Now I create a new, different div element:

var d = document.createElement('div');

This d should now have an empty customObject property, right?

However, if I alert it:

alert (d.customObject.prop);

I get this unexpected result:

bla

How come? When I create a new element shouldn't it have a "blank" instance of HTMLElement.prototype?

( jsfiddle: http://jsfiddle.net/5pgr38mb/ )

EDIT: I am looking for a solution that will work with deep cloning (cloneNode(true)). Meaning - if the custom object has properties on the element or any of its children then that element or child will retain its value in the cloned instance (this is the normal behavior for "native" HTMLElement attributes).

Yuval A.
  • 5,849
  • 11
  • 51
  • 63
  • When you use `HTMLElement.prototype` you set the property for all instance of that object (future and present), meaning all div, ul, li, input, etc tags...not only that but they would share the exact same object reference, so if you changed one of the properties of the `customObject` you would change them all – Logan Murphy Dec 05 '14 at 18:54
  • There's only one `HTMLElement.prototype`, and it only has one `customObject` property. You added the `prop` property to the one and only `HTMLElement.prototype.customObject`. Surely you don't expect each newly-created object to have its own prototype (otherwise, what would be the point of prototypes?). Or do you think that prototype values are somehow "copied" onto each instance are creation time? They are not; property access is based on a *prototype chain*. If property access fails on an instance, we try the same property access on the object's prototype. – apsillers Dec 05 '14 at 18:54
  • How can I create a custom attribute that will be an empty object every time createElement is called, and will be a separate instance on that element (like all normal HTML elements attributes)? – Yuval A. Dec 05 '14 at 18:58

2 Answers2

3

I might use a prototype getter that creates a new object property on the instance when called:

Object.defineProperty(HTMLElement.prototype, 'customObject', {
    enumerable: true,
    get: function() {
        if(this.__thisCustomObject === undefined) {
            this.__thisCustomObject = {};
            // or non-enumerable with:
            //    Object.defineProperty(this, '__thisCustomObject', {
            //        enumerable: false,
            //        value: {}
            //    };
        }
        return this.__thisCustomObject;
    },
    set: function(val) {
        this.__thisCustomObject = val;
    }
});

This way, any time you ask for customObject on an object for the first time, it creates a new object and stores it in that object's __thisCustomObject property. Then, all future requests for customObject use that element's __thisCustomObject property.

Note that this is getter-setter pattern very close to how actual per-element DOM properties are implemented in the Web IDL specification. The only difference here is that the per-element value is stored in a property, rather than a hidden mapping.

Community
  • 1
  • 1
apsillers
  • 112,806
  • 17
  • 235
  • 239
  • i didnt know setters and getters could be so easy in javascript...i like the solution a lot more than my own...my only issue is if you ever set `customObject` to be undefined (very unlikely) the getter will reset it to the default value which may not be a desireable result – Logan Murphy Dec 05 '14 at 19:54
  • 1
    @LoganMurphy If you like you could either remove the setter entirely (so you can never manually set a new object) or use a boolean `__hasSetCustomObject` property or similar. You could make that property non-editable with `configurable: false, writable: false`. – apsillers Dec 05 '14 at 19:56
  • In both yours and Logan Murphy's way - `cloneNode()` will *not* clone the custom object - while "native" attributes does gets cloned correctly (e.g. `className`). I wonder if there's a way to make this work like that, without writing a new clone function. I guess one way would be to override cloneNode and reattach the custom object to the clone...) – Yuval A. Dec 05 '14 at 21:06
2

You could override the document.createElement function. Just open the console (F12 in most browsers) and click run to see the result of this code.

document.createElement = (function () {
    var reference = document.createElement;
    return function (name) {
        var e = reference.call(document, name);
        e.customObject = {
            configurable: true,
            enumerable: true,
            writeable: true,
            value: {}
        };
        return e;
    };
}());

var e = document.createElement('div');
e.customObject.prop = "bla";
console.log(e.customObject.prop);
var d = document.createElement('div');
console.log(d.customObject.prop);
Logan Murphy
  • 6,120
  • 3
  • 24
  • 42
  • I tried your technique and @apsillers' one, and this one performs slightly faster. Thank you both. – Yuval A. Dec 05 '14 at 19:33
  • that is due to the fact that entering and exiting the context of a function is an expensive operation in javascript...so everytime you `get` you enter and exit a function...you could avoid this by caching the `customObject` of an individual div which creates a minor inconvenience...also there is the if in the getter which is tested for every get...keep in mind the test code provided only `gets` it never `sets` – Logan Murphy Dec 05 '14 at 19:50