jQuery architecture

In notebook:
FrontEndMasters Advanced JS fundamentals
Created at:
2015-09-30
Updated:
2015-09-30
Tags:
pattern Fundamentals JavaScript libraries jQuery
The design concept behind jQuery:

  1. Convert DOM query results to loopable collections
  2. create a $.extend() utility to add new utilities like .next or .html()
  3. each utility should return this to make utilities chainable
  4. make ​$(selector)​ create a new object on the first run

1. Convert DOM query results

A node query such as ​document.querySelectorAll("a")​ doesn't return a true array

Learn JS Fundamentals to jQuery & Pure DOM Scripting Exercise 12:Solution


  $ = function(selector){
    var elements = document.querySelectorAll(selector);
    
    for(var i=0; i<elements.length, i++){
        this[i] = elements[i];
    }
    this.length = elements.length;
}
or it can be reduced by "stealing" the array ​push​ method
  $ = function(selector){
    var elements = document.querySelectorAll(selector);
    
    Array.prototype.push.apply(this, elements);
}

note

push is intentionally generic. This method can be used with call() or apply() on objects resembling arrays. The push method relies on a length property to determine where to start inserting the given values. If the length property cannot be converted into a number, the index used is 0. This includes the possibility of length being nonexistent, in which case length will also be created.

2. Create an extend method to add new utilities

   $.extend = function(target, object) {
    for (var prop in object) {
      if (object.hasOwnProperty(prop)) {
        target[prop] = object[prop];
      }
    }
    return target;
 };
then you can just extend jQuery like so:
    $.extend($.prototype, {
    html: function(newHtml) {},
    val: function(newVal) {}
    // ...
}

3. Each utility should return ​this​ to make them chainable

1. Utilities operate on collections, so they have to be applied on each item
So first an ​$.each(collection, cb)​ helper is needed
  $.extend($,{
    each: function(collection, cb) {
      if (isArrayLike(collection)) {
        for (var i = 0; i < collection.length; i++) {
          if (cb.call(this, i, collection[i]) === false) {
            break;
          }
        }
      } else {
        for (var prop in collection) {
          if (collection.hasOwnProperty(prop)) {
            if (cb.call(this, prop, collection[prop]) === false) {
              break;
            }
          }
        }
      }
      return collection;
    }
});
notes
  • usually ​$.each()​ is called with ​this​ as the collection parameter 
  • the iterators ​apply​s (​call​) the callback function with ​this​, the current index in the loop and the value
  • it makes a difference between array-like collections and object properties
so when ​$("div").html()​ is called the ​html()​ is called with ​this​ being the result of ​$("div")​ 
with this
    $.extend($.prototype, {
    html: function(newHtml) {
      if(arguments.length) {
        return $.each(this, function(i, element) {
          element.innerHTML = newHtml;
        });
      } else {
        return this[0].innerHTML;
      }
})
where ​return $.each(this, function(i, element) {​ the ​this​ will be the result of the query e.g. $("div")​ or a better example, ​.find(selector):
Learn JS Fundamentals to jQuery & Pure DOM Scripting Exercise 17: Solution
  // ...extend...
find: function(selector){
    var elements = [];
    $.each(this, funciton(i, el){
        var els = el.querySelectorAll(selector);
        [].push.apply(elements, els);
    });
    return $(elements);
    
}
now, ​this​ will continue to be jQuery and the DOM elements it contains can be stepped over

make ​$(selector)​ create a new object on the first run

to make sure ​this​ points to distinct object and not the ​window​ or other calling context
  $ = function(selector){
    if( !(this instanceof $) ) {
        return new $(selector);
    }
    // ... continue with adding DOM elements
} 
Note:
this jQuery object now has methods like ​.find()​, ​.html("hello")​ and also a loopable collection of DOM elements.

How come they don't conflict?

The DOM nodes are added to numerical references ​this[n] = element​ and also a ​this.length​ property holds the length of all DOM nodes added to the collection. The loop counts from 0 to ​this.length​ and uses ​this[counterindex]​ to retrieve the element.

So this way a jQuery object can "magically" hold a collection DOM nodes and methods.