We're going to start with a simple Bag constructor that accepts a name parameter and creates a getname() method that returns the name.
function Bag(name) {
this.getname = function() {
return name;
};
}
There is already a couple of interesting points to notice:
this refers to whichever Bag instance calls the getname method.name property. The getname() method retains a reference to the name parameter via a closure.We now define a global method that is also called getname(). It simply returns the string "Global". Bonus points are available if you can spot when this global method will come into play. In the browser, window is the global object.
function getname() {
return "Global";
};
The code for our first button test is shown below. Can you predict what will appear in the alert?
document.getElementById("btn1").onclick = function() {
var bag = new Bag("Bob");
alert(bag.getname());
};
A construct that will become useful later is wrapping code inside a self-executing anonymous function. This creates a local scope within the function. It can be particularly useful when sorting out problems involving loops and closures.
For this example, our only purpose is to investigate its effects while we still have a simple class setup.
function Bag(name) {
(function() {
this.getname = function() {
return name;
};
})();
}
Our button code is just the same. Can you predict what will appear in the alert this time?
document.getElementById("btn2").onclick = function() {
var bag = new Bag("Bob");
alert(bag.getname());
};
Okay, that didn't go so well. It caused an error in the page. But why? How?
According to the error console, bag.getname is not a function. In the code for the class we used this.getname when assigning the function. If this is not pointing to bag then what is it pointing to?
Well, try clicking the 'window.getname' button further up the page now. An alert pops up and happily proclaims "Bob".
The global getname() function is meant to return the string "Global".
What has happened is that the this in our this.getname has been broken by the wrapping self-executing anonymous function. That function is not a constructor or a method attached to an object and this becomes null. When that happens, rather than leaving it as null, it is changed to point to the global object.
Our global getname() function was thus overwritten by the assignment to this.getname, just as if we had written window.getname.
Remember that this is just an example. While we're now going to fix an artificial problem we've created ourselves, the principles will be applicable to more complicated and more realistic examples. This example is about as simple as it can be while still demonstrating the mechanisms at work.
We had been relying on this to point to the Bag object instance invoking the getname() method. However, the inner function changed the scope and the context of this. One way to overcome the problem is not to use this in the inner function. While outside of the inner function, we assign this to another variable, that. Now we have a trusty variable name we have chosen rather than the special but fickle word this.
function Bag(name) {
var that = this;
(function() {
that.getname = function() {
return name;
};
})();
}
Note: You may need to refresh the browser to reset the getname() global function if it has been overwritten by 'Test 2'
JavaScript provides ways to set the context of a function, so rather than letting this change scope, we can explicitly set it to point to the Bag object instance.
Using the call() method we pass the object we want to be this into the inner function. Outside of the inner function this does point to the Bag object instance. Hence, we pass this as the parameter to call.
function Bag(name) {
(function() {
this.getname = function() {
return name;
};
}).call(this);
}
Note: You may need to refresh the browser to reset the getname() global function if it has been overwritten by 'Test 2'