Improve Your Code with Smart JavaScript Techniques and Patterns

Smart JavaScript Techniques

In this article we’re going to cover some common, and not so common smart JavaScript techniques and patterns to improve your code in both size and beauty. To follow along with this tutorial it’s important that you have a good understanding of functional programming in JS and regular expressions.

Smart JavaScript Techniques

Smart JavaScript Techniques and Patterns

If you already know about this and that, call, apply, bind and prototypes, then you’re ready to start. Otherwise here’s a few articles to get you started before we begin:

Long Multiple Conditions

This first tip might not be obvious but it’ll help you reduce those long if statements. The idea is to replace logical operators and comparisons with a simple regex test:

If you see yourself doing this:

if (val == 'a' || val == 'b' || val == 'c')

This can be done with regex shorter and nicer:

if (/a|b|c/.test(val))

Another case that you might have to deal with is multiple variables and the AND logical operator, for example:

if (a == 1 && b == 2 && c == 3 && d == 4 && e == 5)

When you have a very long list of variables where you want to compare their values you can create an array with the expected values and another array with the given variables and use the every method to loop and compare. every returns true only if all conditions are met.

var vars = [a,b,c,d,e],
    vals = [1,2,3,4,5];

if (vals.every(function(val,i){ return vars[i] == val }))

This could be abstracted to be reused:

function isTrue(vars, vals) {
  return vals.every(function(val,i){ return vars[i] == val });
}

if (isTrue([a,b,c,d,e], [1,2,3,4,5]))

For the OR logical operator case you’d use the some method which returns true if at least one condition is met. But it’s less common to find long OR patterns like that, it would typically be mixed with ANDs:

if (a == 1 && b == 2 || c == 3 && d == 4 || e == 5 && f == 6)

In this case the logical operator approach seems smart enough.

Text Manipulation

JavaScript’s string tools are quite limited compared to other languages. Consider the case where we have some paragraphs separated by periods and we want to capitalize the first letter of every one of them. Using loops and some string methods we’d do something like this:

var str = 'the sky is blue. apples are green.';

var para = str.split('.'), // get array of paragraphs
    i = 0, len = para.length, p;

for (; i < len; i++) {
  p = para[i].trim(); // make sure there's no additional whitespace
  para[i] = p.substr(0,1).toUpperCase() + p.substr(1, p.length);
}

para = para.join('. ').trim(); // put everything back together

console.log(para);
//^ "The sky is blue. Apples are green."

While this works fine, the obvious smart approach here is to use regular expressions, and all of this becomes incredibly beautiful and concise:

str = str.replace(/([^.]+\.\s?)/g, function(_, para) {
  return para.replace(/^\w/, function(first) {
    return first.toUpperCase();
  });
});

console.log(str);
// ^ "The sky is blue. Apples are green."

Consider another usual case where you want to extract all telephone numbers from some text. Let’s suppose in our string all telephone numbers have the prefix tel:, that way we can grab only telephone numbers and not other irrelevant numbers that the string may contain. It’s evident that regex is the best tool for this task and since there might be multiple telephone numbers we’re going to use a global regex. We could try using match first:

var str = 'Jerry, 26 years old, tel: 555-000-0000. Joy, 19 years old, tel:777-000-0000.';
var numbers = str.match(/tel:\s?([\d-]+)/g);
console.log(numbers); //=> ["tel: 555-000-0000", "tel:777-000-0000"]

But as you can see the output still contains tel: even tough we’re using a capturing group to only get the number. The problem is that match doesn’t capture groups in global regex, for that reason you need to use exec and a confusing while loop pattern that you might have seen before:

var re = /tel:\s?([\d-]+)/g,
    numbers = [], match;

while (match = re.exec(str)) {
  numbers.push(match[1]);
}

console.log(numbers); //=> ["555-000-0000", "777-000-0000"]

That’s usually the code necessary to extract global matches, but it’s not the only option. The replace method takes a function where the first argument is the string itself and the following arguments point to the different capturing groups. The last two arguments are trivial in this case. Knowing this we can make our code smarter:

var numbers = [];
str.replace(/tel:\s?([\d\-]+)/g, function(_, number) {
  numbers.push(number);
});

console.log(numbers); //=> ["555-000-0000", "777-000-0000"] 

As long as you remeber that replace is being used only for the purpose of looping the code becomes obvious, less confusing. We could even make this available to all strings as a new gmatch method:

String.prototype.gmatch = function(regex) {
  var result = [];
  this.replace(regex, function() {
    // extract matches by removing arguments we don't need
    var matches = [].slice.call(arguments, 1).slice(0,-2);
    result.push.apply(result, matches);
  });
  return result;
};

console.log(str.gmatch(/tel:\s?([\d\-]+)/g)) //=> ["555-000-0000", "777-000-0000"]

Now that looks much nicer, proper global match right on the spot.

Generating HTML Markup

A very common case when dealing with HTML strings is creating lists, like li, td, option and others. Typically you’d loop to do this:

var values = ['one', 'two', 'three', 'four', 'five'];

var html = '';
for (var i = 0; i < values.length; i++) {
  html += '<td>'+ values[i] +'</td>';
}

That seems efficient enough but for loops are ugly. We can make this code smarter by using the join array method:

var html = '<td>'+ values.join('</td><td>') +'</td>';

That looks better, but what if we need the index?. In that case the map method can be used in combination with join.

var html = values.map(function(text, i) {
  return '<td>'+ text +': '+ i +'</td>'
}).join('');

When this gets more complex though, we end up having to write many loops with disconnected HTML pieces so everything becomes a little bit of a mess. The solution to this problem is to use templates. There are many templating libraries out there and this is no replacement, but knowing what we know so far we could create a very simple templating helper:

function template(arr, html) {
  return arr.map(function(obj) {
    return html.join('').replace(
      /#\{(\w+)\}/g,
      function(_, match) { return obj[match]; }
    );
  }).join('');
}

It can be used like this:

var people = [
  { name: 'John', age: 25, status: 'Single' },
  { name: 'Bill', age: 23, status: 'Married' },
  { name: 'Mika', age: 17, status: 'Single' }
];

var html = template(people, [
  '<div>',
    '<h1>Name: #{name}</h1>',
    '<h2>Age: #{age}, Status: #{status}</h2>',
  '</div>'
]);

console.log(html);
// ^
// "<div><h1>Name: John</h1><h2>Age: 25, Status: Single</h2></div>\
// <div><h1>Name: Bill</h1><h2>Age: 23, Status: Married</h2></div>\
// <div><h1>Name: Mika</h1><h2>Age: 17, Status: Single</h2></div>"

With this helper you can see how easy it is now to create content in a clear, structured manner.

Going all Functional

JavaScript is a multi-paradigm language with impressive functional programming (FP) features that often make your code more concise and compact as you’ve seen in previous examples.

All latest browsers, including IE9, have support for EcmaScript5 array and object methods. Whenever there’s a for or for..in loop there is an alternative solution with one of these methods.

One advantage of functional looping that might not seem so obvious is that it creates a new scope. This is particularly useful when running asynchronous code inside a loop. Imagine this case:

var arr = ['a','b','c'];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() { console.log(arr[i] +':'+ i); }, i * 1000);
}

The intention here is to log a:0..b:1..c:2 with one second in between but the result ends up being undefined:3..undefined:3..undefined:3. The typical workaround is to create a new scope:

for (var i = 1; i <= 3; i++) {
  setTimeout((function(i) { console.log(arr[i] +':'+ i); }(i)), i * 1000);
}

Now the output is as expected but it looks a bit confusing. Since a forEach loop already creates a new scope things become simpler:

arr.forEach(function(v, i) {
  setTimeout(function() { console.log(v +':'+ i); }, i * 1000);
});

When working with large amounts of data using a functional approach is a great way to improve readability. Take some data like the following:

var users = [
  { 
    name: 'Fred', 
    age: 25, 
    job: 'Developer', 
    membership: 'Gold'
  },
  {
    name: 'John', 
    age: 28, 
    job: 'Cook', 
    membership: 'Platinum'
  },
  {
    name: 'Zoe', 
    age: 32, 
    job: 'Make-up artist', 
    membership: 'Silver'
  },
  {
    name: 'Louise', 
    age: 35, 
    job: 'Cook', 
    membership: 'Silver'
  }
];

With array methods you could simply filter users by any criteria:

var over30 = users.filter(function(user) {
  return user.age >= 30;
});

var cooks = users.filter(function(user) {
  return user.job == 'Cook';
});

This code works and looks good enough but it seems like we’ll have to repeat this pattern again and again to extract information from our object. In order to make our code smarter this time around we’re going to make use of prototypes and some functional patterns we’ve covered before to create a very small DSL to handle our data.

What we want to able to do is something like this:

var over30 = users.where('age').is('>=30');
var cooks = users.where('job').is('Cook');

First let’s take a look at the code and then will see what’s possible with this little library. It can be challenging to follow the code if you don’t have a deep understanding of functional programming in JavaScript but hopefully the comments will make it a little bit clearer.

// An Immediately Invoked Function Expression (IIFE)
// to prevent us from leaking variables to the global scope
(function(win) {

  // Main constructor for the MyStorage object
  function MyStorage(data) {
    this.data = data; // the array containing our data
    this.length = this.data.length; // just a shortcut
  }

  // Shortcut to create new instances of MyStorage
  function stored(data) {
    return new MyStorage(data);
  }

  // Very basic object extender
  function _extend(obj, target) {
    for (var o in obj) target[o] = obj[o];
    return target;
  }

  MyStorage.prototype = {

    // Private:

    // Update data and return a new instance.
    // We need to clone the objects otherwise
    // they would be passed as reference
    _new: function(result) {
      result = result.map(function(o) { return _extend(o, {}); });
      this.length = this.data.length;
      return stored(result);
    },

    // Filter the current data property with a function.
    // The property is passed as parameter into the 'fn' callback
    _filter: function(fn) {
      return this._new(this.data.filter(function(user, i) {
        return fn.call(this, user[this.prop], i);
      }.bind(this)));
    },

    // Public:

    // Get an array with a specific prop from each object
    // or return the data collection otherwise
    get: function(prop) {
      if (prop) return this.map(function() { return this[prop]; });
      return this.data;
    },

    // Set the property to filter or compare to
    where: function(prop) { 
      return this.prop = prop, this; 
    },

    // A shortcut for semantics
    and: function(prop) { return this.where(prop); },

    // Compare the current property to a given condition
    is: function(condition) {
      // Filter by regular expression or string/number
      var regex = condition instanceof RegExp ? condition :
        new RegExp('^'+ condition +'$');

      // Extracts the symbol in cases like '>50' and '<=10'.
      // 'null' and '0' are given as default values if
      // the string doesn't contain a symbol
      var symbol = (/^([<>=%]+)([\d.]+)/.exec(condition) || [0,null,0]);

      // Process the symbol if present
      if (symbol[1]) return this[symbol[1]](+symbol[2]);

      return this._filter(function(prop) { 
        return regex.test(prop); 
      });
    },

    '>': function(v) {
      return this._filter(function(p) { return p > v; });
    },

    '>=': function(v) {
      return this._filter(function(p) { return p >= v; });
    },

    '<': function(v) {
      return this._filter(function(p) { return p < v; });
    },

    '<=': function(v) {
      return this._filter(function(p) { return p <= v; });
    },

    '%': function(v) {
      return this._filter(function(p) { return p % v === 0; });
    },

    // Sort collection by the given function
    // and return a new Templee instance
    sort: function(fn) {
      return this._new(this.data.sort(function() {
        return fn.apply(this, [].slice.call(arguments));
      }.bind(this)));
    },

    // Filter the collection by index
    eq: function(index) {
      return this._new(this.data[index]);
    }

  };

  // Curry most native methods
  'forEach map slice every some reduce'.split(' ').forEach(function(method) {
    MyStorage.prototype[method] = function(fn) {
      return this.get()[method](function() { 
        return fn.apply(arguments[0], [].slice.call(arguments)); 
      }.bind(this));
    };
  });

  win.stored = stored; // expose constructor to user

}(window));

Using Storage is very similar to how you’d use jQuery, very intuitive:

stored(users)
  .where('age').is('>30')
  .and('job').is('Cook').forEach(function(user) {
    console.log(user.name); // Louise
  });

Imagine the possibilities of combining this with the HTML templating system we created before:

var movies = [
  { title: 'Spiderman', score: 7, gross: 90e6 },
  { title: 'Aliens vs Predators', score: 5 , gross: 50e6 },
  { title: 'American Beauty', score: 9.5 , gross: 140e6 },
  { title: '500 Days of Summer', score: 8.5 , gross: 75e6 },
  { title: 'Drive', score: 7.5 , gross: 120e6 },
  { title: '127 Hours', score: 9 , gross: 78e6 }
];

var myMovies = stored(movies);

var featured = template(myMovies.where('score').is('>=8').get(), [
  '<div class="featured-movie">',
    '<h2>#{title}</h2>',
    '<h3>Score: #{score}, Gross: #{gross}</h3>',
  '</div>'
]);

var bigGross = template(myMovies.where('gross').is('>80000000').get(), [
  '<ul class="big-gross-movies">',
    '<li>#{title} <span class="gross">#{gross}</span></li>',
  '</ul>'
]);

$('body').append([
  '<h1>Featured Movies:</h1>'+ featured,
  '<h1>Big Gross Movies:</h1>'+ bigGross,
].join(''));

Other more complex examples are also possible:

// Get all movie titles that start by number
myMovies.where('title').is(/^\d/).get('title'); //=> ["127 Hours", "500 Days of Summer"]

// Chaining
myMovies.where('gross').is('>100000000')
  .and('score').is('>9').get('title') //=> ["American Beauty"]

And of course you can chain all those useful native array methods, forEach, map, slice, every, some, reduce:

// Will log one movie title per second
myMovies.forEach(function() {
  setTimeout(function() { console.log(this.title) }.bind(this), 1000);
});

You can play with this nice demo putting these snippets together: JS Bin

Conclusion

By using functional techniques we can achieve very clean abstracted modular code. Libraries like jQuery and Zepto make use of some of these patterns to make their code smarter, better.

In terms of support, since EcmaScript5 is very well supported nowadays, all of these snippets should work in IE9 and all other modern browsers. To support older browsers you might want to use polyfills to add that functionality.

Hopefully you got some new fresh ideas to try out and make your code smarter and more efficient. Leave me a comment below if you have any questions.

Deals

Iconfinder Coupon Code and Review

Iconfinder offers over 1.5 million beautiful icons for creative professionals to use in websites, apps, and printed publications. Whatever your project, you’re sure to find an icon or icon…

WP Engine Coupon

Considered by many to be the best managed hosting for WordPress out there, WP Engine offers superior technology and customer support in order to keep your WordPress sites secure…

InMotion Hosting Coupon Code

InMotion Hosting has been a top rated CNET hosting company for over 14 years so you know you’ll be getting good service and won’t be risking your hosting company…

SiteGround Coupon: 60% OFF

SiteGround offers a number of hosting solutions and services for including shared hosting, cloud hosting, dedicated servers, reseller hosting, enterprise hosting, and WordPress and Joomla specific hosting.