JavaScript Bracket Notation - Practical Examples
Article Purpose
There are numerous examples online and in books that accurately describe the difference between dot notation and bracket notation in JavaScript. What I find lacking however are practical examples of the less common bracket notation. The purpose of this article is to provide practical examples of using bracket notation.
What Bracket Notation Is
Bracket notation enables a developer to access an object’s properties in JavaScript. It is an alternative to the more common dot notation. The detailed breakdown of each notation is littered across the internet; take your pick to learn more:
Practical Examples
Bracket notation is a useful tool. The examples below provide practical applications of its use. If you have improvement ideas or additional application examples, just let me know (@derekknox or derek [at] derekknox.com) and I’ll update the examples.
Succinct Update Listeners
The main idea behind this example is to determine which of two methods to call and call it in a succint and DRY fashion. When reading code, I commonly see an addEventListeners()
method and a corresponding removeEventListeners()
method when a single combined method could be created using bracket notation. In the updateListener()
method below I'm specifically using 'addEventListener'
and 'removeEventListener'
, but you could instead use the 'on'
and 'off'
methods of jQuery if you were using that library.
This particular approach is abstract enough that it could be leveraged through a Utils class of some kind for all your elements' listeners. Also, it could easily be expanded to take numerous event types and corresponding handlers via arrays.
//element reference
var btn = document.getElementById('btn');
//bracket notation usage
function updateListener(isAdd, element, type, handler) {
var method = isAdd ? 'addEventListener' : 'removeEventListener';
element[method](type, handler);
}
//example handler
function onClick() {
console.log('Winning');
}
//add a click listener to btn with the onClick event handler
updateListener(true, btn, 'click', onClick);
Variable Method Calls
This example expands on the Succinct Update Listeners example. The main difference is that there are more than two potential methods to call and the logic determining which to call can be random or more complex. The example below communicates the approach through an automated drawing program.
//variable method types, cached length, and drawTool definition
var methods = ['drawArc', 'drawCircle', 'drawRectangle', 'drawStar', 'drawChuckNorris'],
len = methods.length,
drawTool = {
drawArc: function() { /*draws an arc to the canvas at a random position */ },
drawCircle: function() { /*draws a circle to the canvas at a random position */ },
drawRectangle: function() { /*draws a rectangle to the canvas at a random position */ },
drawStar: function() { /*draws a star to the canvas at a random position */ },
drawChuckNorris: function() { /*draws Chuck Norris to the canvas at a random position */ }
};
//bracket notation usage
function draw() {
var index = Math.floor(Math.random() * len),
method = methods[index];
drawTool[method]();
}
//automated drawing
setInterval(draw, 500);
Object Properties Iteration
The implementation of this example is found in many places, but it can specifically be found in libraries like jQuery and in design patterns like the Mixin pattern. The main idea is that a developer may not know (or care) what properties exist on an object, but he/she needs access to them. By leveraging bracket notation, it is possible to access each property without knowing its name. The below example is sourced from Addy Osmani in his description of the Mixin pattern. Addy is creating a method called augment()
which allows the properties of one object's prototype to be copied (via bracket notation) to another object's prototype. This is a form of inheritance.
//Extend an existing object with a method from another
function augment( receivingClass, givingClass ) {
//only provide certain methods
if ( arguments[2] ) {
for ( var i = 2, len = arguments.length; i < len; i++ ) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
}
//provide all methods
else {
for ( var methodName in givingClass.prototype ) {
//check to make sure the receiving class doesn't
//have a method of the same name as the one currently
//being processed
if ( !Object.hasOwnProperty.call(receivingClass.prototype, methodName) ) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
//Alternatively (check prototype chain as well):
//if ( !receivingClass.prototype[methodName] ) {
// receivingClass.prototype[methodName] = givingClass.prototype[methodName];
//}
}
}
}
Succinct Cache Check
This example is related to the Object Properties Iteration example above in its bracket notation use. The main idea here is that a developer can succintly gain reference to a previously created object via a cache. If there is no cached object, it is created and then cached. This is an approach that delays the creation of objects until needed (if ever). This approach works best when there is only a single instance of a particular object, but this is not required. The example below demonstrates a drawing program's requestBrush()
method that attempts to return a cached brush. If there is no cached brush, one is created JIT and returned. Bracket notation is leveraged instead of an inferior and lengthy if/else chain or switch/case statement.
//brush creation and cache
var brushCreator = { create: function(name) { /*creates brush and updates cache*/ },
cache: { 'round': { length: 5, diameter: 1, width: 2 } } };
//request a brush
function requestBrush(type, brushCreator){
//bracket notation usage
if(type in brushCreator.cache) {
return brushCreator.cache[type];
}
//not cached, so create and cache
return brushCreator.create(type);
}
//user interaction leads to a requestBrush call
requestBrush('round', brushCreator); //gets cached 'round' brush
//subsequent requestBrush calls
requestBrush('flat', brushCreator); //not cached so creates and caches 'flat' brush
requestBrush('fan', brushCreator); //not cached so creates and caches 'fan' brush
requestBrush('mop', brushCreator); //not cached so creates and caches 'mop' brush
//updated cache sample
/*'round': { length: 5, diameter: 1, width: 2 },
'flat': { length: 4, diameter: 4, width: 4 },
'fan': { length: 3, diameter: 3, width: 5 },
'mop': { length: 2, diameter: 4, width: 4 } */
Other Ideas?
If you have improvement ideas or additional application examples, just let me know (@derekknox or derek [at] derekknox.com) and I’ll update the examples.