Learn jQuery from jqLite

JQuery

Jquery is a great library, it makes manipulating DOM element and browser really simple and easy.
As a web developer, jQuery is our day to day tool. However, sometimes it just too convenience that
we forget how to make web page without it. It is important to go back to see how jQuery handle and wrap
the DOM api and provide the simple interface for us.

JQLite

JQLite

Angular.js comes with a simple compatible implementation of jQuery, called JQLite.
JQLite is used internally for angular.element if user doesn’t include jQuery, as a
replacement for jQuery. It only have 1000 lines of code with lots comments,
So it’s a good starting point to understand how jQuery works.

initialize element

jQuery wrap the html DOM with jQuery object, as in JQLite:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function JQLite(element) {
// if element is jquery object, return element
if (element instanceof JQLite) {
return element;
}
// if element is string, trim it
if (isString(element)) {
element = trim(element);
}
// and if element is html tag, create new jquery object with it.
if (!(this instanceof JQLite)) {
if (isString(element) && element.charAt(0) != '<') {
throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by JQLite! See: http://docs.angularjs.org/api/angular.element');
}
return new JQLite(element);
}
// wrap element with jquery interface
if (isString(element)) {
jqLiteAddNodes(this, jqLiteParseHTML(element));
} else {
jqLiteAddNodes(this, element);
}
}

function jqLiteAddNodes(root, elements) {
if (elements) {
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
? elements
: [ elements ];
// push element to jqLite object
for(var i=0; i < elements.length; i++) {
// push function is borrowed from Array object
root.push(elements[i]);
}
}
}

After initialize, we get a new JQLite object with html DOM inside. then we can call
the jquery api to manipulate to inside element.

ready

One thing jquery provide is $.ready, which will execute the block inside only when
dom is ready

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JQLite.prototype.ready: function(fn) {
var fired = false;

function trigger() {
if (fired) return;
fired = true;
fn();
}

// check if document already is loaded
if (document.readyState === 'complete'){
setTimeout(trigger);
} else {
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
// we can not use JQLite since we are not done loading and jQuery could be loaded later.
// jshint -W064
JQLite(window).on('load', trigger); // fallback to window.onload for others
// jshint +W064
}
}

From the source, we can know jquery check the document.readyState,
DOMContentLoaded event and window.onload event for the ready function.

attributes

jquery provides good api to set the attributes of DOM element, include css,
attr, prop. lets take css as example:

1
2
3
4
5
6
7
8
9
10
// removed ie hacks...
css: function(element, name, value) {
name = camelCase(name);

if (isDefined(value)) {
element.style[name] = value;
} else {
return element.style[name];
}
}

One reason that jquery interface is easy to use is, it provides single function
for both getter and setter. If we pass value, css function is a setter,
otherwise it returns css value of name.

Also, jquery object may include multiple elements, so inside the jquery object,
the css function is wrapped to execute on multiple elements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
JQLite.prototpype[name] = function(arg1, arg2) {
...
if (...) {
// we are a read, so read the first child.
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
}
return value;
} else {
// write, apply to all children
for (i = 0; i < this.length; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
return this;
}
...
}

jQuery object provide both setter and getter on same function, multiple assignment,
short function name and also chaining. It is a really sophisticate api.

events

One thing jquery handled is event, in the time before jquery, people need to
handle different event api between IE and others. Lets see how JQLite handle this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mapping addEventListener (not IE) and attachEvent (IE) events api.
var addEventListenerFn = (window.document.addEventListener
? function(element, type, fn) { element.addEventListener(type, fn, false);}
: function(element, type, fn) { element.attachEvent('on' + type, fn);};

on: function onFn(element, type, fn) {
var events = jqLiteExpandoStore(element, 'events');
// handler will stop propagation and default events
var handle = createEventHandler(element, events);
var eventFns = events[type];

if (!eventFns) {
addEventListenerFn(element, type, handle);
eventFns = [];
}
eventFns.push(fn);
}

conclusion

After review the JQLite source, we can better understand the jQuery API
and how jquery works. And why you can do things such as combine getter and setter,
chaining and multi element assign. Learning those skills can also help us to design better API.

Jimchao

A developer, hacker, traveler and boarder live in New York City. You can follow my code at github.com/rafe

Comments