Eric Allen
Interweb Alchemist
Cardinal Solutions / Front End Fridays
A brief introduction to how jQuery achieves some of its magic and some easy ways we can steal borrow that magic.
Example Repository:
https://github.com/ericrallen/vanilla.git
Clone the repository and checkout the workshop/0 branch
git clone https://github.com/ericrallen/vanilla.git
git checkout workshop/0
Disclaimer: We are going to gloss over and simplify some stuff here.
The basis of jQuery is a single constructor method jQuery() which is commonly rewritten using the shorthand $().
This method selects any elements that correspond to the selector passed to it and returns a reference to itself.
This reference is commonly referred to as a jQuery Collection, which is basically just a NodeList converted into an Array-like Object.
But, because all of jQuery's methods and plugins are defined as part of its prototype, we have access to all of them from the context of this Collection.
Ugh, still boring...
//our main selector function
var $v = function(selector) {
//get our Node List and convert it to an Array
//using the Array.prototype.slice() method
this.elements = [].slice.call(document.querySelectorAll(selector));
//return a reference to $v so that we can continue chaining methods
return this;
};
//map $v.fn to $v.prototype so it's easier to reference
//in plugins and other methods
$v.fn = $v.prototype = {};
//add a method to our prototype that will be accessible
//to our $v object at all times
$v.fn.addClass = function(classString) {
//logic to add class name here
//return a reference to our $v object
//so that we can continue chaining methods
return this;
};
var $v = function(selector) {
Here we are basically just adding a parameter to the window's scope named $v that expects one parameter (our selector string). We could also declare this function as window.$v().
Note: The $ and _ are commonly used by JavaScript libraries as a shorthand because they are the only two non-alphabetic characters that can appear at the start of a function name. Also, having a single character method name can save file size, developer time, and potential typos.
this.elements = [].slice.call(document.querySelectorAll(selector));
Here we are taking the Node List returned by querySelectorAll() and converting it in an actual Array by using the slice() hack trick to have JavaScript convert it into an Array. We are storing this as the elements parameter because we aren't going to be doing things exactly as jQuery does under the hood.
Note: Essentially, Array.prototype.slice() expects the this of the method to be an Array and by passing Function.prototype.call() an Array-like Object, we are having slice() turn it into an Array, which allows us to utilize native Array methods on it. This only works in IE9+.
return this;
And here is the lynch pin of our Fluent Interface Pattern. We return a reference to the current scope which gives us access to the entire $v.prototype and enables us to chain methods.
Note: Method chaining can provide a nice interface for developers working with your libraries and can be used even in cases where prototypes are not utilized. You could do the same thing with many other JavaScript module patterns.
$v.fn = $v.prototype = {};
This is just a little bit of convenience for ourselves and other developers working with the library. By mapping $v.fn to $v.prototype, we save ourselves 7 characters every time we need to reference a prototype method. This saves us not only time and file size (both possibly negligible), but also from potential spelling errors.
Note: Another benefit of this is that it sort of hides the prototypical structure of the library which may feel easier for newer developers who want to write plugins for the library or reference methods from the library's prototype without really understanding prototypes.
$v.fn.addClass = function(classString) {
Here we are just adding a method to our $v prototype. This addClass() method can be referenced by either $v.fn.addClass() or $v.prototype.addClass(), remember the $v.fn is mostly for convenience. Inside of our methods it will just be this.addClass() because we are returning a reference to our $v object.
Note: We could define each method as a property of our initial prototype object that we set earlier using the Object literal notation {}, but by doing it this way we've made it much easier to separate methods out into various files and folders and easily mix and match methods we need for a library tailored to the needs of the current project we are working on.
//check to see if element has given class
$v.fn.hasClass = function(classString, element) {
//if this class exists in the element's className property
if(element.className.indexOf(classString) !== -1) {
return true;
}
//by default, let's assume it doesn't
return false;
};
//check to see if element has given class
$v.fn.hasClass = function(classString, element) {
//if an element was passed to the method
if(element) {
//if this class exists in the element's className property
if(element.className.indexOf(classString) !== -1) {
return true;
}
//by default, let's assume it doesn't
return false;
}
//if no element parameter was passed to the method
//use the first item in our collection
if(this.elements[0].className.indexOf(classString) !== -1) {
return true;
}
//again, let's assume this doesn't have the class
return false;
};
By Eric Allen
Basic introduction to how a bit of how jQuery works under the hood and how we can use those concepts to create our own, jQuery-like library to help people who are less comfortable with JavaScript but comfortable with jQuery start to feel less intimidated by vanilla JavaScript. The initial state of the library focuses on jQuery's class convenience methods.