Utilities
In the sixth and final post of this series, I’m going to cover some of the utility methods that jQuery core provides, and show how you can acheive the same results with vanilla JavaScript. Focus will be on modern browsers, but I’ll also go over achieving the same ends in ancient browsers as well. Modern browsers are considered to be anything newer than and including Internet Explorer 9. Anything older than that is “ancient”.
I know I’ve stated the purpose of this series many times before, and most readers seem to understand. However, I continue to see comments from a few readers who seemed to have missed my point. So, once again, I am writing these posts to teach about the Web API and JavaScript as a language. If you really want to continue using jQuery, have at it. If not, these posts will hopefully give you the confidence and knowledge you need to remove this dependency from your future projects. At the very least, by reading all of this, you will gain a better understanding of the Web API and JavaScript as a language, which is important to your evolution as a web developer (whether you use jQuery or not).
All vanilla JavaScript code below will work in all browsers, unless otherwise noted. As always, edge cases not described here are always possible (both with and without jQuery), as this is the nature of software.
- Is this an Object, Array, or a Function?
- Combine and copy objects
- Iterate over object properties
- Iterate over array elements
- Find an element in an array
- Turn a pseudo-array into a real array
- Modify a function
- Trim a string
- Associate data with an HTML element
- Next
Is this an Object, Array, or a Function?
Due to the fact that JavaScript follows weak and dynamic value typing, you may find the need to check the type of a value and react accoringly based on the actual type. What follows is code that can help you achieve this goal in both jQuery and vanilla JavaScript.
jQuery
jQuery provides a few functions to identify a value as either an Object
, Array
, or a Function
. They are appropriately and intuitively named. Each function returns a boolean
.
$.isFunction(someValue);
$.isPlainObject(someValue);
$.isArray(someValue);
vanilla JavaScript
To determine if a value is a function, we can make use of the typeof
operator.
// is this a function?
typeof someValue === 'function';
Determining if a value is an object is a bit less intuitive, but still doable in one line. We must include a null-check as some browsers will report null
as an Object
. The second half of the check uses the toString
function of the Object
prototype on the value to be tested.
// is this an object?
someValue != null &&
Object.prototype.toString.call(someValue) === "[object Object]";
Checking for an Array
is similar (but simpler) than the check for an Object
. Plus, you’ll see that a convenience method exists as an alternative for modern browsers.
// works in modern browsers
Array.isArray(someValue);
// works in all browsers
Object.prototype.toString.call(someValue) === "[object Array]";
Combine and copy objects
Suppose you have two objects, o1
, and o2
:
var o1 = {
a: 'a',
b: {
b1: 'b1'
}
},
o2 = {
b: {
b2: 'b2'
},
c: 'c'
};
What if you want to take o2
and append it to o1
, or create a new object that is the aggregate of both objects? The combined object should look like this:
{
a: 'a',
b: {
b1: 'b1',
b2: 'b2'
},
c: 'c'
}
jQuery
// updates o1 with the contents of o2
$.extend(true, o1, o2);
// creates a new object that is the aggregate of o1 & o2
var newObj = $.extend(true, {}, o1, o2);
If we just want to create a copy of one of the objects, we can do that too, also using $.extend
:
var copyOfO1 = $.extend(true, {}, o1);
vanilla JavaScript
Accomplishing this task without jQuery involves iterating over the properties in o2
. For each property with an object value, we’ll need to iterate over that too, making a recursive call. As we encounter non-object values in o2
, we’ll assign them to the corresponding property in o1
.
// our helper function
function extend(first, second) {
for (var secondProp in second) {
var secondVal = second[secondProp];
// Is this value an object? If so, iterate over its properties, copying them over
if (secondVal && Object.prototype.toString.call(secondVal) === "[object Object]") {
first[secondProp] = first[secondProp] || {};
extend(first[secondProp], secondVal);
}
else {
first[secondProp] = secondVal;
}
}
return first;
};
// example use - updates o1 with the content of o2
extend(o1, o2);
// example use - creates a new object that is the aggregate of o1 & o2
var newObj = extend(extend({}, o1), o2);
As you can see above, we can create a new object that contains the combination of o1
and o2
, without modifying either of the source objects, simply by creating a copy of o1
, and extending that with the properties of o2
. In case you missed it, here’s a simpler example that focues specifically on copying o1
:
var copyOfO1 = extend({}, o1);
Note: the simple code used above to iterate over object properties is fine for this example, but you may want to read the next section, which covers iterating over object properties for a more complete understanding of how this should be done with vanilla JavaScript.
Iterate over object properties
Let’s say we have the following Object, called parentObject
:
var parentObject = {
a: 'a',
b: 'b'
};
…and then we create myObject
, which inherits the properties of parentObject
:
var myObject = Object.create(parentObject);
myObject.c = "c";
myObject.d = "d";
Now, myObject
has 4 properties, but only ‘c’ and ‘d’ belong to myObject
. Properties ‘a’ and ‘b’ are part of parentObject
, which is on the same prototype chain as myObject
. Most likely, when we want to iterate over the properties of myObject
, we only want the properties that belong to this object, and not any that belong to parentObject
.
jQuery
$.each(myObject, function(propName, propValue) {
// handle each property...
});
The above code will iterate over all of the myObject
properties, ignoring any properties that do not directly belong to myObject
.
vanilla JavaScript
In the previous section, while discussing copying and combining objects, I included a barebones method of iterating over an object’s properties. In case you need to deal with something more complex or want to exploit native approaches available in modern browsers, I’ll go over some other approaches, as well as some things you should be aware of when dealing with object property iteration.
For reference, here is the simple for...in
loop presented in the “Combine & Copy Objects” section, now iterating over myObject
:
for (var myObjectProp in myObject) {
// deal with each property in the `myObject` object...
}
So, if we outputted every property encountered by the loop above, we’d see the properties on myObject
, as well as the properties of parentObject
. Remember, parentObject
and myObject
are on the same prototype chain, and myObject
inherits all of the properties from parentObject
. But, this is not what we want. We only want the properties that myObject
owns! To acheive this, we need to exclude any other properties, and this can be done via the hasOwnProperty
method.
// works in all browsers
for (var prop in myObject) {
if (myObject.hasOwnProperty(prop)) {
// deal w/ each property that belongs only to `myObject`...
}
}
All objects have a hasOwnProperty
method, as this method is available on Object.prototype
.
But wait! There’s another solution, specific to modern browsers. ECMAScript 5 defines a new function: Object.keys(someObj)
, which returns an array of properties on the passed object. Also, this array will only contain the properties owned by the passed object.
// works in modern browsers
Object.keys(myObject).forEach(function(prop) {
// deal with each property that belongs to `myObject`...
});
Note that I’m using forEach
on the returned array of property names above. I’ll talk more about iterating over arrays in the next section.
Iterate over array elements
Given an array, like this:
var myArray = ['a', 'b', 'c'];
…we’d like to be able to simply iterate over each of the elements in this array.
jQuery
Iterating over arrays in jQuery is a lot like iterating over object properties. In both cases, just use the jQuery.each
method:
$.each(myArray, function(arrayIndex, arrayValue) {
// handle each array item...
});
vanilla JavaScript
This isn’t much harder without jQuery, regardless of the browser. We can simply iterate over the elements in the array using a simple for
loop:
// works in all browsers
for (var index = 0; i < myArray.length; i++) {
var arrayVal = myrray[index];
// handle each array item...
}
But that doesn’t look nearly as nice as jQuery, does it? ECMAScript 5 to the rescue again! We can make use of Array.prototype.forEach
in modern browsers instead:
// works in modern browsers
myArray.forEach(function(arrayVal, arrayIndex) {
// handle each array item
}
Notice that the order of the parameters passed to your callback function by Array.prototype.forEach
differs from that of jQuery.each
. I actually prefer the ordering used by forEach
as it allows me to omit the index
parameter if I only care about the values. Note that there are a lot of other useful functions on Array.prototype
in ES5 and beyond.
Find an element in an array
Here’s I’ll cover two similar operations on an array:
- Find the index of an array that contains a specific value.
- Find all elements in an array that satisfy a particular condition.
Let’s again use a simple array:
var theArray = ['a', 'b', 'c'];
jQuery
To find the index of an array for a specific value, we can use the inArray
function:
var indexOfValue = $.inArray('c', theArray);
indexOfValue
will be -1 if the value represented by the first parameter, 'c'
, is not found in theArray
. Otherwise, it will be the index of theArray
where the passed value was found. In this case, that index is 2
.
If we want to use jQuery to find all elements in an array that satisfy a particular condition, we can use the grep
function:
var allElementsThatMatch = $.grep(theArray, function(theArrayValue) {
return theArrayValue !== 'b';
});
allElementsThatMatch
will equal ['a', 'c']
as our filter function excludes any value that matches ‘b’.
vanilla JavaScript
If you’d like to find the index of an array for a specific value, AND you are using a modern browser, you can make use of the Array.prototype.indexOf
method:
// works in modern browsers
var indexOfValue = theArray.indexOf('c');
Sadly, the indexOf
method didn’t come about until ECMAScript 5. So, if you are suffering with a requirement to support an ancient browser, such as IE 8, you must iterate over the elements of the array, using a for
loop, until you find the desired element. For example:
// works in all browsers
function indexOf(array, valToFind) {
var foundIndex = -1;
for (var index = 0; index < array.length; index++) {
if (array[index] === valToFind) {
foundIndex = index;
break;
}
}
return foundIndex;
}
// example use
indexOf(theArray, 'c');
If you want to find all values that satisfy a specific condition in vanilla JS, you can make use of the shiny new Array.prototype.filter
method in ES5:
// works in modern browsers
var allElementsThatMatch = theArray.filter(function(theArrayValue) {
return theArrayValue !== 'b';
});
That actually looks a bit nicer than jQuery’s grep
method!
If you need to deal with ancient browsers, such as IE 8, you’ll need to write a bit more code:
// works in all browsers
function filter(array, conditionFunction) {
var validValues = [];
for (var index = 0; index < array.length; i++) {
if (conditionFunction(theArray[index])) {
validValues.push(theArray[index]);
}
}
}
// use example
var allElementsThatMatch = filter(theArray, function(arrayVal) {
return arrayVal !== 'b';
})
Turn a pseudo-array into a real array
In JavaScript, there are real arrays:
var realArray = ['a', 'b', 'c'];
…and “pseudo”-arrays:
var pseudoArray1 = {
1: 'a',
2: 'b',
3: 'c'
length: 3
};
There are some native pseudo-arrays as well, such as NodeList
, HTMLCollection
, and FileList
, to name a few. These are not real arrays in that they are not on the same prototype chain as Array
. That is, they inherit nothing from Array.prototype
because they are not arrays. In fact, they are just objects. But, due to the length
property, you can treat them as an array, in some respects, by iterating over their “elements” using a for
loop, just as you would any real array.
If you are dealing with a pseudo-array in an ancient browser, such as IE 8, it probably doesn’t matter much, since you’ll need iterate over the properties/elements using a for
loop whether it is a real array or not. But, suppose you are using a modern browser, and you want to make this object that looks very much like an array act like an array. Perhaps you want to use forEach
, or map
, or filter
. Or perhaps you must pass it to an API method that expects a real array.
jQuery
You can convert a pseudo-array to a real array in jQuery using the makeArray
method:
var realArray = $.makeArray(pseudoArray);
vanilla JavaScript
If you’d like to transform a pseudo-array to a real array, and make all methods on Array.prototype
available, you can use this trick:
var realArray = [].slice.call(pseudoArray);
If you only care to use a specific array method on a pseudo-array, such as forEach
you can do this instead:
[].forEach.call(pseudoArray, function(arrayValue) {
// handle each element in the pseudo-array...
});
Why does this work? There’s an excellent Stackoverflow answer that provides a nice explanation.
Modify a function
There are two ways to modify an existing function:
- Change its context.
- Create a new function with some pre-determined arguments.
In both cases, you would want to take an existing function and create a new function based on the original.
1 - Change a function’s context
To demonstrate #1, let’s propose we have an event handler. By default, the context of an event handler in modern browsers and jQuery is the element that started the event (except for inline event handlers, which are a bad idea anyway). But what if you want your event handler to assume a different context? For example, say you have some public instance variables attached to the context of a function, and you want to have easy access to these variables inside your event handler:
function Outer() {
var eventHandler = function(event) {
this.foo = 'buzz';
}
this.foo = 'bar';
// attach `eventHandler`...
}
var outer = new Outer();
// event is fired, triggering `eventHandler`
Without any changes, outer.foo === 'bar'
. Since the foo
property is being set in the context of an event handler, the element that triggered the event is actually receiving a foo
property with a value of ‘buzz’. But we want outer.foo === 'buzz'
!
jQuery
It is possible to re-assign the context of a function in jQuery using the proxy
function. To make this work, we simply need to wrap the function in a call to jQuery’s proxy
utility method:
function Outer() {
var eventHandler = $.proxy(function(event) {
this.foo = 'buzz';
}, this);
this.foo = 'bar';
// attach `eventHandler`...
}
var outer = new Outer();
// event is fired, triggering `eventHandler`
Now, when the event handler is triggered, outer.foo === 'buzz'
.
vanilla JavaScript
Modern browsers provide a bind
function on Function.prototype
. This allows you to change the context of a function, just like jQuery’s proxy
, but in a slightly more elegant way:
// works in modern browsers
function Outer() {
var eventHandler = function(event) {
this.foo = 'buzz';
}.bind(this);
this.foo = 'bar';
// attach `eventHandler`...
}
var outer = new Outer();
// event is fired, triggering `eventHandler`
We simply call bind
on the function, pass the desired context, and the resulting function assigned to eventHandler
is associated with the context of our Outer
function, which means outer.foo === 'buzz'
when the event handler is ultimately invoked inside our Outer
instance.
If you must support an ancient browser, like IE 8, you’ll need to take one of two alternate approaches. The first involves adding an implementation of bind
to Function.prototype
if the browser does not provide a native implementation (such as with IE 8 and older). Mozilla Developer Network provides some code for such a polyfill. I’m not going to repeat the code here, as it is displayed quite nicely on MDN. Simply invoking that function on page load will ensure that a bind
utility function will be available for all functions.
Another option is to take a completely different approach, and store the value of this
in a variable inside of the Outer
function. You can then reference that variable (instead of this
) inside of eventHandler
to ensure the correct property is updated. For instance:
// works in all browsers
function Outer() {
var self = this,
eventHandler = function(event) {
self.foo = 'buzz';
};
this.foo = 'bar';
// attach `eventHandler`...
}
var outer = new Outer();
// event is fired, triggering `eventHandler`
When the eventHandler
is invoked, our instance of Outer
, represented by the outer
variable, will contain the expected value of foo
, which is ‘buzz’. This is a feasible solution in all browsers.
2 - Create a new function with some pre-determined arguments
Another way to phrase the method demonstrated here is “partial function application” or “currying”. Instead of silly pointless examples involving functions that add numbers together, let’s try to demonstrate something at least a bit more realistic.
Say we have a utility function that is part of a shared library used among many of our applications. This function logs messages to a remote server for aggregation and further evaluation. The function takes an application ID, a level (such as “info” or “error”), and a log message. It looks like this:
function log(appId, level, message) {
// send message to central server
}
We’ve pulled in the shared library containing this function into our app, and our application has an ID of “master-shake”. Now, every time we log a message in our app, do we really want to pass the same first parameter, our app ID, which never changes? We certainly could, or, we can create a new function that fills in this parameter automatically for us. In other words, we want to call a function and only pass in the variable data: the level and message, with the same end result.
jQuery
As before, we’ll use jQuery’s proxy
function:
var ourLog = $.proxy(log, null, 'master-shake');
// example use
ourLog('error', 'dancing is forbidden!');
Invoking ourLog
will more or less pass the application ID (“master-shake”), as well as the specified error and message, to the original function.
vanilla JavaScript
We can again use the bind
function for modern browsers. As before, if an ancient browser is required, a shim can be used to help us out.
var ourLog = log.bind(null, 'master-shake');
// example use
ourLog('error', 'dancing is forbidden!');
You’re probably wondering about the significance of the null
parameter we’ve passed to jQuery’s proxy
and native JS’s bind
function. We don’t care much about context in this example, but passing null
as the context to bind
will set the value of this
inside the bound function to the global function (window
if we’re in a browser). In the case of jQuery’s proxy
function, a null
context parameter (at least as of jQuery 1.9) means that the proxied function will assume a value of this
equal to the context of the calling function.
Trim a string
Given this string:
" hi there! "
We want to trim all of the leading and trailing whitespace to produce this:
"hi there!"
jQuery
We can using jQuery’s trim
function to do this:
$.trim(' hi there! ');
The string, sans leading & trailing whitespace, will be returned by the function call above.
vanilla JavaScript
In modern browsers, we can use the trim
function on String.prototype
:
// works in modern browsers
' hi there! '.trim();
For ancient browsers, we aren’t so lucky, and have to resort to a regular expression to remove the leading and trailing spaces:
// works in all browsers, but needed in IE 8 and older
' hi there! '.replace(/^\s+|\s+$/g, '');
You can roll this into a function that will use the trim
method if it exists, else the regexp if we’re dealing with an ancient browser:
// works in all browsers
function trim(string) {
if (string.trim) {
return string.trim();
}
return string.replace(/^\s+|\s+$/g, '');
}
Associate data with an HTML element
You may find yourself wishing to track data (objects, strings, numbers, and even other elements) in the context of a specific HTML element. The safest way to do this, cross browser, to avoid memory leaks associated with circular references between elements, is to not attach this data directly to the element’s associated JavaScript object as a property. If you’re only tracking string, number, or simply object values, then this isn’t much of an issue, but once you involve elements as data values as well, older browsers may spring memory leaks. For more information on that, feel free to read Joel Webber’s old but interesting points on the subject.
To keep things simple, straightforward, and safe, we’re going to avoid storing anything other than strings and numbers directly on the element. So, we’ll need to tag our elements with an ID/key, and attach that key to our data in an object maintained in a data store apart from the element.
Let’s say we have two elements, and when we click on one, we want the other to be either hidden or made visible (the opposite of its current state).
<div id="one">one</div>
<div id="two">two</div>
This example is a bit contrived, admittedly, but it illustrates our topic rather simply.
jQuery
jQuery’s data
method will create an ID for each element we want to tag with data, add that ID as a property to the element’s JavaScript object, and use that ID to tie the element to the data in a central object maintained by the library.
// make the elements aware of each other
$('#one').data('partnerElement', $('#two'));
$('#two').data('partnerElement', $('#one'));
// on click, either hide or show the partner element
$('#one, #two').click(function() {
$(this).data('partnerElement').toggle();
});
vanilla JavaScript
There are a couple ways to do this in vanilla JS. The first will work in all browsers, but requires a bit more code. For this first approach, let’s create a couple functions. One will tag an element with an ID and store that ID along with the data in an object. The other will retrieve data, given an element:
// works in all browsers
var data = (function() {
var lastId = 0,
store = {};
return {
set: function(element, info) {
var id;
if (element.myCustomDataTag === undefined) {
id = lastId++;
element.myCustomDataTag = id;
}
store[id] = info;
},
get: function(element) {
return store[element.myCustomDataTag];
}
};
}());
…and now let’s use it based on our previously stated requirements:
// make the elements aware of each other
var one = document.getElementById('one'),
two = document.getElementById('two'),
toggle = function(element) {
if (element.style.display !== 'none') {
element.style.display = 'none';
}
else {
element.style.display = 'block';
}
};
data.set(one, {partnerElement: two});
data.set(two, {partnerElement: one});
// on click, either hide or show the partner element
// remember to use `attachEvent` in IE 8 and older, if support is required
one.addEventListener('click', function() {
toggle(data.get(one).partnerElement);
});
two.addEventListener('click', function() {
toggle(data.get(two).partnerElement);
});
Yes, this is a hell of a lot more code than the jQuery example. I know. Remember, this is about understanding JavaScript, not necessarily about counting lines of code. But the awesome thing about JavaScript as a language is that it continues to evolve and make our lives easier. ECMAScript 6 brings a new collection, called a WeakMap
. A WeakMap
can contain keys that are objects, and values that are anything. Keys are “weakly” held by the collection. This means that they are eligible for garbage collection by the browser if nothing else references them. So, we can use the reference elements as keys!
While WeakMap
is only supported in the latest and greatest browsers (IE 11+, Chrome 36+, Safari 7.1+) and Firefox 6+, we can perhaps make use of it if the browser provides such support. If we rely only on the WeakMap
, we can eliminate our data helper entirely, and our entire set of code looks like this instead:
// works only in the latest browsers
// make the elements aware of each other
var weakMap = new WeakMap(),
one = document.getElementById('one'),
two = document.getElementById('two'),
toggle = function(element) {
if (element.style.display !== 'none') {
element.style.display = 'none';
}
else {
element.style.display = 'block';
}
};
weakMap.set(one, {partnerElement: two});
weakMap.set(two, {partnerElement: one});
// on click, either hide or show the partner element
// remember to use `attachEvent` in IE 8 and older, if support is required
one.addEventListener('click', function() {
toggle(weakMap.get(one).partnerElement);
});
two.addEventListener('click', function() {
toggle(weakMap.get(two).partnerElement);
});
Of course, we can’t rely entirely on WeakMap
, yet (unless we only support IE 11+). So, we’ll probably instead want to continue to use our data provider, relying on WeakMap
only if the browser provides an implementation:
// works in all browsers
var data = window.WeakMap ? new WeakMap() : (function() {
var lastId = 0,
store = {};
return {
set: function(element, info) {
var id;
if (element.myCustomDataTag === undefined) {
id = lastId++;
element.myCustomDataTag = id;
}
store[id] = info;
},
get: function(element) {
return store[element.myCustomDataTag];
}
};
}());
Next
“You Don’t Need jQuery (anymore)!” is complete! I hope you’ve found some use in these posts. I personally have enjoyed the format of this blog, and I think a series of posts about a central topic is interesting and useful in other instances.
I plan to write another similar blog series next, this time covering cross-domain browser-based communication. Some of the posts in that series will likely cover: the same-origin policy, CORS, JSONP, the Web Messaging API, and content security policies, among other topics. Stay tuned, and I’ll be sure to link to the new blog after I’ve completed the first post.