Building your own jQuery

04-02-2018 • Javascript

In this post we will explore how we can build our own little DOM manipulation library with a syntax similar to that of jQuery.

The constructor function

function sQuery(selector) {
  // return an instance of sQuery
  if (!(this instanceof sQuery)) return new sQuery(selector)
  // find the elements
  this._elements = document.querySelectorAll(selector)
  // stores the length of the found elements for easy access
  this.length = this._elements.length
}

This is the main function of our library. Because we do not want to use the new operator every time we create an new instance of sQuery, we return a new instance even when the function is called without new. If you want to learn more about about how this works, read my post Javascript, instantiate without new.

We then use querySelectorAll to find the elements that match the given selector and store the resulting NodeList in this._elements, we also store the number of elements in this.length for easy access.

If you really care about performance you should use the getElementById, getElementsByClassName or getElementsByTagName functions, these can be up to a hundred times faster than querySelectorAll. According to my benchmarks querySelectorAll is still faster then jQuery in most cases, the exception being, finding elements by id.

Iterating over the found elements

querySelectorAll returns a NodeList, which is a "Array like" object. We need an easy way to iterate over it, sadly NodeList.forEach was only introduced recently and browser support for the function isn't widespread. That is why we are going to create a small helper function to iterate over the NodeList.

sQuery.prototype._forEach = function (callback) {
  // iterate over the elements
  for (var i = 0; i < this._elements.length; i++) {
    // call the callback function with the
    // current element as a parameter
    callback(this._elements[i])
  }
}

Adding some functionality

Ok, now it's time to add some functionality to our library. We will start with a html function similar to that of jQuery. According to the jQuery documentation the html function does the following: Gets the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element.

sQuery.prototype.html = function (htmlString) {
  // check if a parameter is passed and if it is a string
  // we could (or should) do even more elaborate checks here
  if (htmlString && typeof htmlString === 'string') {
    // loop through all elements and set the elements innerHTML
    this._forEach(function(element) {
      element.innerHTML = htmlString
    })
    // return this sQuery Object for chaining functions
    return this
  } else {
    // return the innerHTML of first element
    if (this._elements.length > 0) return this._elements[0].innerHTML
  }
}

By returning this, which holds a reference to the current sQuery object, we can chain together functions, just like we can in jQuery.

Next we are going to implement the on function, this function will add an eventlistener to all matched elements.

sQuery.prototype.on = function (eventName, handler) {
  // loop through all elements and add addEventListeners
  this._forEach(function(element) {
    element.addEventListener(eventName, handler)
  })
  // return this sQuery Object for chaining functions
  return this
}

I benchmarked both functions against their jQuery equivalent. Our version of the html function is much faster than the jQuery version. The on function however, is up to 70 times slower than jQuery on. This is because jQuery optimizes it's on function to add the eventListener only once per event type per element. Unless you are planning to add several thousand eventlistener per second, our function will do just fine.

Ok, that will be enough functionality for this post, you can of course add as much (or little) functionality as you see fit.

Using our library

The last thing we will do before we start using our library is to create a nice little alias. We are simply going to call it "s", but you could also call it "$" to have the exact same syntax as jQuery.

window.s = sQuery

We are now ready to start using our library.

<button id="button"></button>
s('#button').html('click me').on('click', function(){
  console.log('you clicked the button!')
})

Looks quite similar to jQuery, doesn't it?