It should now be universally accepted that extending the DOM directly (via Element.prototype
, Node.prototype
etc.) is a bad idea. To combat the inherent problems with doing this, quite a few libraries (jQuery, BBC Glow, etc.) employ the “wrapper” technique which involves taking a bunch of DOM elements and stuffing them into an array-like object that inherits from a safely-extendible prototype.
If you want to have a go at creating your own jQuery-like DOM library, here’s something to get you started:
window.myWrapper = (function(){ // "cache" useful methods, so they don't // have to be resolved every time they're used var push = Array.prototype.push, slice = Array.prototype.slice, toString = Object.prototype.toString, isArray = function(o) { toString.call(o) === '[object Array]'; }, toArray = (function(){ try { // Return a basic slice() if the environment // is okay with converting NodeLists to // arrays using slice() slice.call(document.childNodes); return function(arrayLike) { return slice.call(arrayLike); }; } catch(e) {} // Otherwise return the slower approach return function(arrayLike) { var ret = [], i = -1, len = arrayLike.length; while (++i < len) { ret[i] = arrayLike[i]; } return ret; }; })(); function NodeList(elems) { this.length = 0; push.apply( this, elems.nodeType ? [elems] // Single node : isArray(elems) ? elems // Real array : toArray(elems) // NodeList/Array-like-object ); } function myWrapper(elems) { // Instantiate and return new NodeList return new NodeList(elems); } myWrapper.NodeList = NodeList; NodeList.prototype = { each: function(fn) { for (var i = -1, l = this.length; ++i < l;) { fn.call(this[i], this[i], i, l, this); } return this; } }; return myWrapper; })(); |
There’s no selector engine, but it’s easy enough to slot one in:
function myWrapper(elems) { // E.g. using Sizzle as the selector engine return new NodeList( typeof elems === 'string' ? Sizzle(elems) : elems ); } |
Example of usage:
myWrapper(document.getElementsByTagName('div')).each(function(){ alert(this.id); }); // Etc. |
Extending NodeList
:
myWrapper.NodeList.prototype.applyCSS = (function(){ var hasOwn = Object.prototype.hasOwnProperty; return function(props) { for (var i = -1, l = this.length; ++i < l;) { for (var p in props) { if (hasOwn.call(props, p)) { this[i].style[p] = props[p]; } } } return this; }; }()); // Usage: myWrapper(document.body).applyCSS({ background: 'red', color: 'yellow' }); |
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Thanks for the post. It’s pretty informative for those who look under the hoods of libraries or like you said, those would would like to start something for themselves. Even though it’s just a foundation, I just wish you could of commented a little more to explain more of the what and why.
Looks like
myWrapper
is created through undeclared assignment instead of being declared properly. You must have missed it?Converting nodelist to array with help of try-catch is a rather crude approach. We can do better than that, by feature testing at load-time if
Array.prototype.slice/push
can convert nodelist to array. If you care about performance, you can forktoArray
(or whatever it’s called) method at load-time as well.Something along these lines (untested, but hopefully shows the point):
Thanks for your commments!
@Jeff, I’ve added some comments to the code. Good point 🙂
@kangax, I’ve modified it as per your recommendations. It seems to work… I didn’t bother with
canConvertNodelistToArray
— I just assumed if it hasn’t thrown an exception within thetry{}
then I can continue to return theslice()
function:Very nice work. Useful wrapping tutorial
Interesting for loop construction
What are your views on the nano-optimisation possibilities of something like:
or even
Probably best to stop now…
We can do better than that, by feature testing at load-time if Array.prototype.slice/push can convert nodelist to array