Built.io Blog

10 JavaScript Concepts Node.js Programmers Must Master

,

This post first appeared on infoworld.com

With JavaScript and the V8 engine at the core, an event-driven architecture, and scalability out of the box, Node.js has quickly become the new de facto standard for creating web applications and SaaS products. Many frameworks like Express, Sails, and Socket.IO enable users to quickly bootstrap applications and focus only on the business logic.

Of course Node.js owes much to JavaScript for its enormous popularity. JavaScript is a multiparadigm language that supports many different styles of programming, including functional programming, procedural programming, and object-oriented programming. It allows the developer to be flexible and take advantage of the various programming styles.

But JavaScript can be a double-edged sword. The multiparadigm nature of JavaScript means that nearly everything is mutable. Thus, you can’t brush aside the probability of object and scope mutation when writing Node.js code. Because JavaScript lacks tail call optimization (which allows recursive functions to reuse stack frames for recursive calls), it’s dangerous to use recursion for large iterations. In addition to pitfalls like these, Node.js is single threaded, so it's imperative for developers to write asynchronous code.

JavaScript can be a boon if used with care—or a bane if you are reckless. Following structured rules, design patterns, key concepts, and basic rules of thumb will help you choose the optimal approach to a problem. Which key concepts should Node.js programmers understand? Below I’ll share the 10 JavaScript concepts that I believe are most essential to writing efficient and scalable Node.js code.

1. Immediately invoked function expressions

An immediately invoked function expression (IIFE) is a function that's executed as soon as it's created. It has no connection with any events or asynchronous execution. You can define an IIFE as shown below:

(function(){
                // all your code here
                // ...
            })();

The first pair of parentheses function(){...} converts the code inside the parentheses into an expression.The second pair of parentheses calls the function resulting from the expression. An IIFE can also be described as a self-invoking anonymous function. Its most common usage is to limit the scope of a variable made via var or to encapsulate context to avoid name collisions.

2. Closures

A closure in JavaScript is an inner function that has access to its outer function's scope, even after the outer function has returned control. A closure makes the variables of the inner function private. A simple example of a closure is shown below:

var count = (function () {
            var _counter = 0;
            return function () {return _counter += 1;}
})();
count();
count();
count();
// the counter is now 3

The variable count is assigned an outer function. The outer function runs only once, which sets the counter to zero and returns an inner function. The _counter variable can be accessed only by the inner function, which makes it behave like a private variable.

3. Prototypes

Every JavaScript function has a prototype property that is used to attach properties and methods. This property is not enumerable. It allows the developer to attach methods or member functions to its objects. JavaScript supports inheritance only through the prototype property. In case of an inherited object, the prototype property points to the object’s parent. A common approach to attach methods to a function is to use prototypes as shown below:

function Rectangle(x, y) {
            this._length = x;
            this._breadth = y;
}
Rectangle.prototype.getDimensions = function () {
            return { length : this._length, breadth : this._breadth };
};
Rectangle.prototype.setDimensions = function (len, bred) {
            this._length = len;
            this._breadth = bred;
};

4. Private properties, using closures

JavaScript lets you define private properties by using the underscore prefix as shown in the above example. However, this does not prevent a user from directly accessing or modifying a property that is supposed to be private.

Defining private properties using closures will help you solve this problem. The member functions that need access to private properties should be defined on the object itself. You can make private properties using closures as shown below:

function Rectangle(_length, _breadth) {
            this.getDimensions = function () {
            return { length : _length, breadth : _breadth };
            };
            this.setDimension = function (len,bred) {
            _length = len;
            _breadth = bred
            };
}

5. The Module pattern

The Module pattern is the most frequently used design pattern in JavaScript for achieving loosely coupled, well-structured code. It allows you to create public and private access levels. One way to achieve a Module pattern is shown below:

var Direction = (function() {
  var _direction = 'forward'
  var changeDirection = function(d) {
            _direction = d;
  }
  return {
            setDirection: function(d) {
            changeDirection(d);
            console.log(_direction);
            }
  };
})();
Direction.setDirection('backward');   // Outputs: 'backward'
console.log(Direction._direction);

The Revealing Module pattern is similar to the Module pattern wherein the variables and methods that need to be exposed are returned in an object literal. The above example can be written using the Revealing Module pattern as follows:

var Direction = (function() {
  var _direction = 'forward';
  var _privateChangeDirection = function(d) {
            _direction = d;
  }
  return {
            setDirection: _privateChangeDirection
  };
})();

6. Hoisting

JavaScript moves variables and function declarations to the top of their scope before code execution. This is called hoisting. Regardless of where you place the declaration of functions and variables in your code, they are moved to the top of their scope by the interpreter. This may or may not be where you want them. If not, then your program will have errors.

Variable declarations are processed before any code is executed. Ironically, undeclared variables do not exist until they are assigned a value. This causes all undeclared variables to become global variables. Though function declarations are hoisted, function expressions are not hoisted. JavaScript has an order of priority while hoisting variables and functions.

The priority is given below from higher to lower:

  • Variable assignment
  • Function declaration
  • Variable declarations

To avoid bugs, you should declare your variables and functions at the beginning of every scope.

7. Currying

Currying is a method of making functions more flexible. With a curried function, you can pass all of the arguments that the function is expecting and get the result, or you can pass only a subset of arguments and receive a function back that waits for the rest of the arguments. A simple example of a curry is given below:

var myFirstCurry = function(word) {
  return function(user) {
            return [word , ", " , user].join("");
  };
};
var HelloUser = myFirstCurry("Hello");
HelloUser("Rahul"); // Output: "Hello, Rahul"

The original curried function can be called directly by passing each of the parameters in a separate set of parentheses one after the other as shown below:

myFirstCurry("Hey, wassup!")("Rahul"); // Output: "Hey, wassup!, Rahul"

8. The apply, call, and bind methods

It’s imperative for any JavaScript developer to understand the difference between the call, apply, and bind methods. The three functions are similar in that their first argument is always the “this” value, or context, that you want to give the function you are calling the method on.

Of the three, call is the easiest. It's the same as invoking a function while specifying its context. Here’s an example:

var user = {
     name: "Rahul Mhatre",
     whatIsYourName: function() {
     console.log(this.name);
     }
};
user.whatIsYourName(); // Output: "Rahul Mhatre",
var user2 = {
     name: "Neha Sampat"
};
user.whatIsYourName.call(user2); // Output: "Neha Sampat"

apply is nearly the same as call. The only difference is that you pass arguments as an array and not separately. Arrays are easier to manipulate in JavaScript, opening a larger number of possibilities for working with functions. Here is an example using apply and call:

var user = {
     greet: "Hello!",
     greetUser: function(userName) {
     console.log(this.greet + " " + userName);
     }
};
var greet1 = {
     greet: "Hola"
};
user.greetUser.call(greet1,"Rahul") // Output: "Hola Rahul"
user.greetUser.apply(greet1,["Rahul"]) // Output: "Hola Rahul"

The bind method allows you to pass arguments to a function without invoking it. A new function is returned with arguments bounded preceding any further arguments. Here is an example:

 var user = {
                greet: "Hello!",
                greetUser: function(userName) {
                console.log(this.greet + " " + userName);
                }
           };
           var greetHola = user.greetUser.bind({greet: "Hola"});
           var greetBonjour = user.greetUser.bind({greet: "Bonjour"});
           greetHola("Rahul") // Output: "Hola Rahul"
           greetBonjour("Rahul") // Output: "Bonjour Rahul"

9. Memoization

Memoization is an optimization technique that speeds up function execution by storing results of expensive operations and returning the cached results when the same set of inputs occur again. JavaScript objects behave like associative arrays, making it easy to implement memoization in JavaScript. For example, we can convert a recursive factorial function into a memoized factorial function as shown below:

function memoizeFunction(func) {
  var cache = {};
  return function() {
            var key = arguments[0];
            if(cache[key]) {
            return cache[key];
            }
            else {
            var val = func.apply(this, arguments);
            cache[key] = val;
            return val;
            }
  };
}
var fibonacci = memoizeFunction(function(n) {
  return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
});

10. Method overloading

Method overloading allows multiple methods to have the same name but different arguments. The compiler or interpreter determines which function to call based on the number of arguments passed. Method overloading is not directly supported in JavaScript. But you can achieve something very much like it as shown below:

function overloadMethod(object, name, fn){
            if(!object._overload){
            object._overload = {};
            }
            if(!object._overload[name]){
            object._overload[name] = {};
            }
            if(!object._overload[name][fn.length]){
            object._overload[name][fn.length] = fn;
            }
              object[name] = function() {
                        if(this._overload[name][arguments.length])
                        return this._overload[name][arguments.length].apply(this, arguments);
              };
}
function Students(){
  overloadMethod(this, "find", function(){
            // Find a student by name
  });
  overloadMethod(this, "find", function(first, last){
            // Find a student by first and last name
  });
}
var students = new Students();
students.find(); // Finds all
students.find("Rahul"); // Finds students by name
students.find("Rahul", "Mhatre"); // Finds users by first and last name

As you become well-versed with Node.js, you'll notice there are many ways to solve almost every problem. But taking the right approach is critical. A wrong approach will result in multiple side effects like patchy or buggy applications or regressions that force you to rewrite the entire logic. On the flip side, the right approach will lay the foundation for a robust, efficient, and scalable application.

The 10 JavaScript concepts described in this article are basics every Node.js developer should know. But they are the tip of the iceberg. JavaScript is powerful and complex. The more you use it, the more you will understand how vast JavaScript really is. A better understanding of such an extensive language will certainly help you avoid mistakes. In the meantime, get the basics right and you’ll see great results.

Subscribe to our blog