May 4, 2012

Sharing prototypes in JS

jQuery is certainly awesome, but thanks to modern browsers and the prototypical nature of Javascript we can have many of its features just by interchanging methods from different prototypes.
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And now we can chain the forEach method after anything that returns a NodeList (or HTMLCollection in IE9) object.
document.getElementsByClassName("links").forEach(function(node){
 console.dir(node)
})
document.querySelectorAll(".menu").forEach(function(node){
 console.dir(node)
})
And many others work as well:
NodeList.prototype.map = HTMLCollection.prototype.map =  Array.prototype.map
NodeList.prototype.filter = HTMLCollection.prototype.filter = Array.prototype.filter
But there are some methods that do not work the way you may expect:
NodeList.prototype.pop = Array.prototype.pop
var collection = document.querySelectorAll(".example")
console.log( collection.length ) // 13
collection.pop()
console.log( collection.length ) // 13
But there is a way to avoid this problem and similar ones: instead of directly using the Array prototype we explicitly convert it to an array with a simple method.
NodeList.prototype.toArray = function(){
 return Array.prototype.slice.call(this) 
}

document.getElementsByTagName("a").toArray()
Yeah, is not as sexy but it works a lot better and nobody will have to guess if it returns a NodeList or an Array.

Just to cheer you up after that disappointment lets implement the find method on arrays, similar to the one jQuery uses.
Array.prototype.find = function(selector){
 var matches = [];
 var each = Array.prototype.forEach;
 each.call(this,function(ele){  
  if(ele.querySelectorAll){
   each.call(ele.querySelectorAll(selector),function(ele){
    if(matches.indexOf(ele) === -1){
     matches.push(ele)
    }
   })
  }
 }) 
 return matches;
}
Now we can do cool stuff, like:
[document].find(".related a").map(function(ele){
 // your awesome code
})

3 comments:

  1. The ECMAscript standard doesn't guarantee that Array.prototype.slice.call works on non-Array objects. In fact, that throws an error in IE.

    ReplyDelete
    Replies
    1. As i said the the beginning this is aimed for modern browsers, and Array.prototype.slice.call works in EI9. But thanks to you i noticed that i have to use the HTMLCollection prototype instead of NodeList in IE9.

      Delete
  2. Awesome. jQuery features with 20x the speed.

    ReplyDelete

You can use [CODE][/CODE] to post Javascript code.
You can start a sentence with ">" to make a quotation.