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

2

There are 2 best solutions below

2
On

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);

3
On

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.