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.
- 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.
- 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