5

I want to hook onto the document.createElement function in such a way that, every time I create a div element, my hook will attach a "foo" attribute to the div. This is what I have currently:

<script>
    window.onload = function () {
        console.log("document loaded");
        document.prototype.createElement = function (input) {
            var div = document.createElement(input);
            console.log("createElement hook attached!");
            if (input == "div")div.foo = "bar";
            return div;
        }

        document.body.addEventListener('onready', function () {
            var div = document.createElement("div");
            console.log(div.foo);
        });

    }
</script>

When I run this in Chrome, I get an error saying

Uncaught TypeError: Cannot set property 'createElement' of undefined test.html:4 window.onload

(I changed the line number in the error message above to match my code)

What am I wrong here? How can I fix this?

Discombobulous
  • 1,112
  • 2
  • 14
  • 25
  • Extending the `prototype` of `document`! Wow, good luck with that.. – Snow Blind Jul 30 '12 at 18:56
  • 1
    Messing with DOM objects is a very painful thing to do across browsers. One of the reasons that the Prototype JS library did not make it. You might want to create a wrapper objects around any you wish to extend. http://perfectionkills.com/whats-wrong-with-extending-the-dom/ – S. Albano Jul 30 '12 at 19:15
  • Don't build the dom manually and especially without libraries. Unless this is a personal learning project, use a templating engine and a library to do DOM manipulations or you're begging for a maintenance and portability nightmare. It should never be the case that intercepting createElement is necessary, and that may not always be possible in the future. – Justin Summerlin Jul 30 '12 at 19:22

2 Answers2

13
  • document doesn't have a .prototype, since it's an instance object and not a constructor function
  • you are calling the new document.createElement in your new function, it would end up in recursion. You need to store reference to the old one somewhere, and call that.
  • You are setting a property instead of attribute
  • This is extremely fragile thing to do and not guaranteed to work. It appears to work in chrome and firefox, but won't work in old IE

Try this

document.createElement = function(create) {
    return function() {
        var ret = create.apply(this, arguments);
        if (ret.tagName.toLowerCase() === "div") {
            ret.setAttribute("foo", "bar");
        }
        return ret;
    };
}(document.createElement)

http://jsfiddle.net/NgxaK/2/

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • This will not modify divs already on the page. Also, apply is unnecessary and is significantly slower than directly calling the original `createElement`. – Justin Summerlin Jul 30 '12 at 19:19
  • 1
    I don't think he expected it to modify divs already existing. As for `.apply`, I am simply guaranteeing that the arguments are passed exactly to `document.createElement` as they were passed to the new function. I doubt it's that much slower than `.call`. – Esailija Jul 30 '12 at 19:20
  • `call` is also quite slow, somewhere around 10x slower than a direct invocation. `apply` is typically 3-5x slower, even. There's no fast way to wrap a global that has its invocation scope protected, which is why I didn't suggest something like this. – Justin Summerlin Jul 30 '12 at 19:28
  • Probably unnecessary for most cases, but `HTMLDocument.prototype.createElement` is available for overriding as far back as IE8. Current browsers should override `Document`, however (note the capital "D"). – Brett Zamir Apr 11 '14 at 13:03
  • @JustinSummerlin if use direct invocation, wouldn't it miss `this` in its execution? – Boyang Aug 22 '16 at 03:30
1

I would suggest not overwriting existing functions, as they may be made read-only in the future. I would suggest post-processing the DOM (a quick traversal for divs is faster than intercepting the creation of every element) and/or modifying the code that inserts divs to add your attribute. Alternatively, if you really want to modify created nodes, a much better method would be Mutation Observers (HTML5):

http://updates.html5rocks.com/2012/02/Detect-DOM-changes-with-Mutation-Observers

This is a much better option than using the mutation events that have been deprecated in HTML4, and overwriting globals is generally considered a bad practice unless you're creating a shim or a polyfill.

Justin Summerlin
  • 4,938
  • 1
  • 16
  • 10