Pages

Monday, February 24, 2014

JavaScript Best Practices: Why to avoid global variables and objects in JavaScript?

Learning AngularJS; Guide for beginners:

In this article I would try to explain the issue can be occurred due to global variable declaration if it is not declared global intentionally.

As we discussed in previous article JavaScript Best Practices : Strict mode In JavaScript if you are not using strict mode of ECMAScript 5 then assigning value to a variable which is not declared yet, then a global variable of that name will automatically be created.



See demo here.



(function() {
  myVar = 'Hello, Undeclared Variable!';
  alert(foo)  //=>; Hello, Undeclared Variable
})();

alert(myVar)  //=>; Hello, Undeclared Variable

So it is important to always declare your variables before initialization.

See demo here.

(function() {
  var myVar = 'Hello, Undeclared Variable!';
  alert(foo)  //=> Hello, Undeclared Variable
})();

//on accessing out side of scope ReferenceError: myVar is not defined
try {
  alert(myVar)
} 
catch (e) {
  alert("Error :" + e);
}

How it can cause error?


When global variables sneak into your code they can induce troubles. Particularly in applications with concurrency.

In the following example two different function using counter in loop without declaration which causes both to point same global variable.

see here without concurrency

var countOnetoTen = function() {
console.log("countOnetoTen started");
for (counter = 1; counter <= 10; counter += 1) {
         console.log(counter);
     }
 };
  countOnetoTen();  //=> 1 2 3 4 5 6 7 8 9 10

var countEleventtoTwenty = function() {
console.log("countEleventtoTwenty started");
for (counter = 1; counter <= 10; counter += 1) {
         console.log(counter+10);
     }
 };
  countEleventtoTwenty(); //=> 11 12 13 14 15 16 17 18 19 20// 
Both loops increment counter at the same time, which causes strange behavior in concurrency. With small amount of loops counter it is not observable, but it can be problematic in any case. As if two function working concurrently and accessing the same global variable. There can be a situation
  1. countOnetoTen() started set counter = 1
  2. countOnetoTen() : printed counter and increment counter++ //counter = 2
  3. countOnetoTen() : printed counter and increment counter++ //counter = 3
  4.  countEleventtoTwenty() started and set counter = 1
At this point counter is set to 1 while countOnetoTen() already have printed 1 2 and in next iteration it will find counter set to 1 and will print 1 again.
window.setTimeout(countEleventtoTwenty , 10);
window.setTimeout(countOnetoTen, 10);  //=> 2 3 7 8 9
  
this Keyword as global object

Sometime you can use 'this' in method definitions to refer to properties of the method's object.
var obj = {
prop: 'foo',
myFun: function() {
alert(this.prop);
}
};

obj.myFun();  //=> print foo
But 'this' does not conform the normal rules of scope in JavaScript. One might expect 'this' to be available with the same value via closure in the callback specified inside the method here. see here demo
var obj = {
  prop: 'foo',
  myFun: function() {
    window.setTimeout(function() {
      alert(this.prop);
    }, 3000);
  }
};

obj.myFun(); //=> alert undefined
Here in callback 'this' got bound to the global object which do not contains the definition of prop. To get around this, assign the object reference to a regular variable that will have the same value inside the callback definition. see here demo
var obj = {
prop: 'foo',
myFun: function() {
  var that = this;
  window.setTimeout(function() {
    alert(that.prop);
  }, 3000);
 }
};

obj.myFun();  //=> alert foo
The keyword 'this' is actually dynamically assigned whenever a function is invoked. When a function is invoked as a method, i.e. obj.method(), 'this' is bound to 'obj'. But when a function is invoked by itself 'this' is bound to the global object.
var text = 'Hello, world!';
var printText() {
alert(this.text);
}

printText();  //=> Hello, world!
This is true even of functions that were defined as a method.
var obj = {
  prop: 'foo',
  myFun: function() {
   alert(this.prop);
  }
};
When the subroutine is invoked without reference of object ie obj with it, 'this' becomes the global namespace.
var myFun = obj.myFun;
myFun();  //=> undefined
Method invocation and function invocation are two of the invocation patterns in JavaScript. A third is apply invocation, which gives us control over what 'this' will be assigned to during function execution.
myFun.apply(obj, null);  //=> foo
'apply' is a method on Function. The first argument is the value that 'this' will be bound to. Successive arguments to apply are passed as arguments to the function that is being invoked. The last invocation pattern in JavaScript is a constructor invocation. This Pattern was projected to offer a means to make new objects that would seem familiar to programmers who are used to programming with classes.
var Duck = function(name) {
this.name = name;
};
Duck.prototype = {
query: function() {
  alert(this.name + ' says, "quack"');
}
};
When a instance is created with new keyword in front of it, a new object is initiated and is linked to 'this' keyword when function executed.
var donald= new Duck('Donald');
  donald.query();  //=> donald says "quack"
When a new object is created with 'new', the prototype of the new object is set to the prototype of the constructor function. So the new object inherits all of the attributes of the constructor's prototype value. In this case, new duck objects inherit the 'query' method from Duck.prototype.
var daffy = new Duck('Daffy');
daffy.query();  //=> Daffy says "quack"
If a constructor function is called without the 'new' keyword, it is invoked with the ordinary function invocation pattern. So 'this' is assigned to the global object instead of to a newly created object. That means that any attributes assigned to the new object by the constructor function become global variables!
var gotcha = Duck('gotcha!');
gotcha.query();  //=> TypeError: gotcha has no properties
Constructor invocation is pretty complicated and prone to disastrous global variable creation. Here is a neater path to produce new objects that inherit from other targets This defines Object.create, a method that simplifies the behavior of the 'new' keyword. This method was invented by Douglas Crockford.
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function() {};
F.prototype = o;
return new F();
};
}
Object.create(obj) returns a new object that inherits all of the attributes of obj. The 'duck' prototype object here defines a 'clone' method that wraps around Object.create to customize new 'duck' objects as they are created.
var duck = {
query: function() {
print(this.name + ' says "quack"');
},
clone: function(name) {
var newDuck = Object.create(this);
newDuck.name = name;
return newDuck;
}
};

var buffy = duck.clone('buffy');
buffy.query();  //=> buffy says "quack"
In addition to inheriting 'query', new ducks also inherit 'clone'.
var buffy2 = buffy.clone('buffy2');
buffy2.query();  //=> buffy2 says "quack"
Methods and attributes are inherited, not copied. If you change the definition of 'clone' on 'duck' at this point, the change will be reflected in duck objects that have already been created.
buffy2.hasOwnProperty('clone')  //=> false
buffy.hasOwnProperty('clone')  //=> false
duck.hasOwnProperty('clone')  //=> true

No comments:

Post a Comment