Wednesday, December 3, 2014

Fundamentals: Get back to basics

Its already been pretty well accepted that JavaScript is not a toy language.  The JavaScript ecosystem has exploded, with many different frameworks and libraries.  Chances are that there are already many different libraries for whatever functionality you need to implement.  Best practices, coding standards, etc have been established.  So why is there seemingly a need to re-invent the wheel?

Just because you can do it, doesn't mean you should


JavaScript is a very powerful language, a lot has been written about more advanced JavaScript techniques.  There are many excellent resources for learning about these techniques.

See JavaScript Patterns by Stoyan Stefanov

The power of these techniques is leveraging them when appropriate, not just for the sake of doing it.  Lets look at some examples:

Immediate functions


Unnecessary wrapping of code with an immediate function to enforce strict mode (example 1)

(function () {
  'use strict';
  angular.module('app').
    controller('AppController', ['$scope', function ($scope) {
      $scope.myVar = 'myvar';

      $scope.fooMethod = function () {

      };
    }]);
  
}());

Simple strict mode declaration

angular.module('app').
  controller('AppController', ['$scope', function ($scope) {
    'use strict';

    $scope.myVar = 'myvar';

    $scope.fooMethod = function () {
    };
  }]);


In the above example, we see two ways of enforcing strict mode on our Angular Controller. In example 1, we are using an immediate function to put the block into strict mode.  The example above is a trivial example, but has the following drawbacks.
  1. We are creating another activation record to push onto the stack, since JavaScript is function scoped.  Its trivial, but there's no need to create an enclosing scope just to wrap the block with strict mode.
  2. Novice developers may be confused by the immediate function syntax.  In any project where you work with other developers, readability and clarity are of utmost importance.

Abusing the "this" keyword

In the short time that I have used angular, I feel very strongly that one of the greatest advantages of angular is that we don't need to use the this keyword which is often poorly understood by most developers, and can still be confusing to more experienced JavaScript developers.  

JavaScript Scoping

One of the biggest pitfalls new JavaScript developers run into is believing that JavaScript is block scoped, it is not block scoped!  


Using the this keyword can easily lead to confusion lets look at some examples.  Imagine we create a factory object, which has the method increment, this increment method should update its count property.  At some point later in time, a controller re-uses this method to increment factory.count.  Using the this keyword can result in an unexpected result.
var factory = {
    count: 0,
    increment: function () {
      this.count++;
    }
  };

  var controller = {
    count: 10,
    increment: factory.increment
  };

  controller.increment();
  console.log(factory.count); // 1
  console.log(controller.count); // 11

See live code in JS BIN

The above example is trivial, but its not hard to imagine examples where we can get unexpected results.  Pretty much anywhere we use the this keyword, we can achieve the same result with closure.
  var factory = {
    count: 0,
    increment: function () {
      factory.count++;
    }
  };

  var controller = {
    count: 10,
    increment: factory.increment
  };

  controller.increment();
  console.log(factory.count); // 1
  console.log(controller.count); // 10

Using this in angular controller makes unit testing weird


angular.module('app').
  controller('AppController', ['$scope', function ($scope) {
    var my = this;
    
    my.count = 0;
    my.increment = function () {
      my.count++;  
    };

  }]);

// jasmine specs

inject(function ($rootScope, $controller) {
  ctrl = $root$scope.$new();
  $controller('AppController', {
   $scope: ctrl
  });  
});

it('ctrl has the increment method', function () {
  expect(ctrl.increment).toBeDefined(); // fails
  expect(ctrl.count).toBe(0); // fails
});

We see that by using the this keyword, accessing the functions to test is not available the standard way we create unit tests for angular in jasmine.

Closure nightmare

An example combining immediate functions, and abuse of the this keyword follows below.

(function () { // immediate function introduces a closure scope
  'use strict'; 

  angular.module('app').
    controller('MyCtrl', ['$scope', MyCtrl]).
    factory('MyFactory', ['OtherService', MyFactory]);

  function MyCtrl($scope) {
    var that = this; // depends on controller()

    $scope.count = 0;
    
    that.factoryIncrement = MyFactory.increment;

    that.increment = function () {
      that.factoryIncrement();      
      that.count++;
    };

  }

  function MyFactory(OtherService) {
    return {
      count: 0,
      increment: function () {
        this.count++;  // depends on factory()
      };
    };
  }

  function initialize() {
    var that = this; // 'this' depends on context
    that.count = 10;
  };

  this.initialize = initialize; // this -> immediate fn

  this.initialize();
}());

In the above example this has 4 possible meanings!

Takeaways

Use advanced JavaScript techniques when appropriate.  Like all advances in programming paradigms, the power of Angular, is the fact that abstracts away unnecessary details and allows the developer to focus on the Business logic.  

No comments:

Post a Comment