jQuery architecture
The design concept behind jQuery:
3. Each utility should return
1. Utilities operate on collections, so they have to be applied on each item
- Convert DOM query results to loopable collections
- create a
$.extend()
utility to add new utilities like.next
or.html()
- each utility should return
this
to make utilities chainable - 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 arrayLearn 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 itemSo 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 withthis
as the collection parameter - the iterators
apply
s (call
) the callback function withthis
, the current index in the loop and the value - it makes a difference between array-like collections and object properties
so when
with this$("div").html()
is called the html()
is called with this
being the result of $("div")
$.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 overmake $(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.