When coding in VanillaJS, I usually create shortcuts for document.querySelector and document.querySelectorAll. I also like to declare D as a shortcut to document:
const D = document
const $ = D.querySelector.bind(D)
const $$ = (selector, startNode = D) => [...startNode.querySelectorAll(selector)]
It's also good to know that the $ and $$ functions are already built-in aka Command Line API when you open the JS console in Devtools. 
With that dollar functions, you can already use a syntax that is similar to jQuery:
<button id="button">click me!</button>
$('#button').onclick = () => {
  alert('You clicked me!')
}
If you want to play with multiple elements, the $$ shortcut to document.querySelectorAll comes into play.
<button> button 1 </button>
<button> button 2 </button>
<button> button 3 </button>
$$('button').map(btn => {
  btn.style.backgroundColor = 'red'
})
When it comes to event handling, having an on method can be useful:
Array.prototype.on = function(type, listener, options) {
  this.map(el => {
    if (el instanceof Element) {
      el.addEventListener(type, listener, options)
    }
  })
  return this // for chaining
}
This way, you can register multiple event handlers on multiple elements at once:
$$('button').on('click', e => {
  const btn = e.target
  alert('You clicked ' + btn.textContent)
}).on('mouseover', e => {
  const btn = e.target
  btn.style.backgroundColor = 'red'
}).on('mouseout', e => {
  const btn = e.target
  btn.style.backgroundColor = 'blue'
})
 

 
    
Top comments (15)
Instead of
startNode||DI would recommend default arguments – it's exactly the reason they were added to the languageGood point :)
Another option is to attach the
onmethod plusArray.prototype.mapto theNodeListprototype instead:This keeps the Array prototype clean =).
Short but sweet post, Lea! 🙌
Found it while researching our story on vanilla JS. Also been working on an open source list of paid & free quality resources for devs to learn vanilla JS--open for PRs. Maybe I should add an "article" section with content such as yours?
Hi Franck, thank you! I love your list of resources for Vanilla JS. Feel free to add it as you like :)
Hi, ignoring with which document you're working is a quick way to bang your head to the wall. Always keep a reference to which document you're in:
A shorthand for document, why not:
Everything's nice now, you need to work off-line ?
In this case, another option is to define another shortcut for documents other than
document. For example, you can move the $ function definition inside a function that takes adocumentas a parameter:I like that you monkey-patch
Array#on- but is there ever an instance that aNodeListreturned fromquerySelectorwouldn't return anElement?What I'm trying to ask is:
Is
if (el instanceof Element)a necessary check?The reason I did the check is that
NodeListgets converted into anArrayvia the$$function.NodeLists are read-only and always containElementelements, so it is safe to calladdEventListeneron them without the check.Arrays are not read-only and the array elements are not safe from reassignment.Great tips
Great article!
I am not sure how I feel about altering built-in prototypes, I generally like to keep those clean. But the jquery-like syntax and the ability to chain event listeners without having to actually use jQuery is super sweet! 👌
We should avoid mutating the Array prototype though. That's why MooTools SmooshGate happened. Good post though, short and simple
Love it!
Definitely going to incorporate this in my snippet library.
Another Lea happens to have made something similar: blissfuljs.com/
Yes, Bliss.js by Lea Verou also provide the
$and$$shortcuts forquerySelectorandquerySelectorAll, plus more functions that make it a fully-fledged, powerful and lightweight framework.