Skip to content

Instantly share code, notes, and snippets.

@kenbod
Last active August 29, 2015 14:14
Show Gist options
  • Save kenbod/be0c9ce0a79bcd342465 to your computer and use it in GitHub Desktop.
Save kenbod/be0c9ce0a79bcd342465 to your computer and use it in GitHub Desktop.
Understanding Javascript's this Variable
var a = 42;
var b = function() {
return this.a;
};
var c = function(d) {
var a = 23;
return d();
};
b(); // returns 42;
c(b); // returns 42;
// This code assumes you are executing it using Node.
// Use window.new_var if you are running it in a browser.
console.log(global.new_var); // prints undefined
var b = function() {
this.new_var = 23;
};
console.log(global.new_var); // prints undefined
b();
console.log(global.new_var); // prints 23
var increment = function() {
this.age = this.age + 5;
};
var person1 = {
age: 42,
makeMeOlder: increment
};
var person2 = {
age: 23,
makeMeOlder: increment
};
person1.makeMeOlder();
person2.makeMeOlder();
console.log(person1.age); // prints 47
console.log(person2.age); // prints 28
var increment = function(delta) {
this.age = this.age + delta;
};
var person = {
age: 42
}
increment.call( person, 5 ); // or increment.apply( person, [5] );
console.log( person.age ); // prints 47
// demonstrate "controller" that uses this
var create_person = function() {
this.age = 42;
this.makeYounger = function() {
this.age = 23;
}
}
// As long as no one sets an execution context
// explicitly, the controller functions as expected
var controller = new create_person();
console.log("=========================================");
console.log("controller with this and implicit binding");
console.log("controller.age before: " + controller.age); // prints 42
controller.makeYounger();
console.log("controller.age after : " + controller.age); // prints 23
console.log("=========================================");
// recreate controller
controller = new create_person();
var younger = controller.makeYounger; // capture method
var person = {}; // create empty object
console.log("controller with this and explicit binding");
console.log("before call()");
console.log("controller.age: " + controller.age); // prints 42
console.log("person.age : " + person.age); // prints undefined
younger.call( person );
console.log("after call()");
console.log("controller.age: " + controller.age); // prints 42 not 23
console.log("person.age : " + person.age); // prints 23 not undefined
console.log("=========================================");
// demonstrate "controller" that uses self
var create_person2 = function() {
var self = this;
self.age = 42;
self.makeYounger = function() {
self.age = 23;
}
}
// It no longer matters how the controller is called.
var controller = new create_person2();
console.log("controller with self and implicit binding");
console.log("controller.age before: " + controller.age); // prints 42
controller.makeYounger();
console.log("controller.age after : " + controller.age); // prints 23
console.log("=========================================");
// recreate controller
controller = new create_person2();
var younger2 = controller.makeYounger; // capture method
var person2 = {}; // create empty object
console.log("controller with self and explicit binding");
console.log("before call()");
console.log("controller.age: " + controller.age); // prints 42
console.log("person.age : " + person2.age); // prints undefined
younger2.call( person2 );
console.log("after call()");
console.log("controller.age: " + controller.age); // prints 23
console.log("person.age : " + person2.age); // prints undefined
console.log("=========================================");

Understanding Javascript's this variable

When discussing AngularJS in lecture this week, we saw that many of our controllers used the following trick to ensure that functions properly updated a controller's variables.

angular.module('myApp').controller('MyController', [function() {
  var self = this;
  self.myVar = 42;
  self.updateVar = function() {
    self.myVar = 23;
  };
}]);

I was asked why I did this and I gave a (somewhat mysterious) single-word answer: Closures. But the answer is a bit more complicated than that and the other aspect that we have to understand is Javascript's this variable.

I won't attempt to provide a complete explanation of the this variable. People write entire books on that subject! But, I will attempt to show the basics.

Default Binding

The first important thing to know about this is that you never have to declare it. Javascript automatically declares it for you and sets its value automatically. Indeed, Javascript won't let you assign a value to this. If you do, it will get angry and throw a ReferenceError at you.

this = 42; // BOOM!

The second thing to know is that this points at the execution context for a function. The trick is understanding how that context is determined. One simple mistake is to think that the execution context is the same thing as the function's scope: that is what variables are in the function's lexical scope. But that's not correct (and see the book I referenced above for more details.) Instead, the execution context is determined by how the function is invoked.

If a function is called directly then this points at the global execution context. (Note: I'm not going to get into a discussion about strict mode and how that changes Javascript's behavior for this example. See the book for details!)

// Example One
var a = 42;

var b = function() {
  return this.a;
};

var c = function(d) {
  var a = 23;
  return d();
};

b(); // returns 42;

c(b); // returns 42;

In this example, when we call b directly, it returns the a in the global execution context. When we call b again in c it does not matter that c defined its own a variable, we are still calling b directly (even though in c, we refer to b via the alias d) and, when you call a function directly, this binds to the global execution context.

This behavior means that if you assign a property to this in a function, then that property is created in the global context. For instance, the following code creates a new variable in the global scope.

// Example 2
// This code assumes you are executing it using Node.
// Use window.new_var if you are running it in a browser.

console.log(global.new_var); // prints undefined

var b = function() {
  this.new_var = 23;
};

console.log(global.new_var); // prints undefined

b();

console.log(global.new_var); // prints 23

Implicit Binding

A function can also be called as a method on an object. When this happens, the object is said to be implicitly bound as the execution context of the associated function. A function in this context is able to change the values of that object's properties via this. For instance:

// Example 3
var increment = function() {
  this.age = this.age + 5;
};

var person1 = {
  age: 42,
  makeMeOlder: increment
};

var person2 = {
  age: 23,
  makeMeOlder: increment
};

person1.makeMeOlder();
person2.makeMeOlder();

console.log(person1.age); // prints 47
console.log(person2.age); // prints 28

Here we define a freestanding function but store it as the value of a property on two objects. We then invoke the function on each object via the property name: person1.makeMeOlder();. The function increment() is invoked and its execution context is set to be (in this case) the person1 object. Thus when increment references this.age, it is referencing the age property of person1.

Explicit Binding

Functions can be invoked in two additional ways:

  1. In the context of a new statement: here the function is used as a constructor to initialize a new object. We will not be discussing this case any further.

  2. In the context of an explicit binding: here the execution context for a function is selected ahead of time and then used when that function is called. There are three ways of doing this: bind(), call(), and apply(). We will look at just one of these: call(). call() and apply() function the same way, they differ only in the parameters they accept. bind() is a utility function that creates what is known as a hard binding; hard bindings are out-of-scope for this particular discussion.

Let's see an example of an explicit binding and then return to our original question about why we configured our AngularJS controllers in the way shown above.

// Example 4
var increment = function(delta) {
  this.age = this.age + delta;
};

var person = {
  age: 42
}

increment.call( person, 5 ); // or increment.apply( person, [5] );

console.log( person.age ); // prints 47

First, we define a function called increment(). It accepts a delta and increments this.age by that value. Second, we created an object called person with a property age set initially to 42. Second, we invoke call() ON THE FUNCTION and we pass it the object that should explicitly be bound as the execution context when increment is executed which is what happens next. We also supply the parameter that should be passed to increment as the second parameter of call(). (We show in a comment how to achieve the same effect using apply(). apply() simply expects any parameters for the function to be supplied in an array.) Thus, increment.call( person, 5 ) is equivalent to person.increment(5) (assuming we defined increment as a property on person). The advantage of the former is that we can pass any object with an age property to increment.call() and it will update the age property by the delta that is passed. Finally, third, we use a console.log() statement to show that person's age was indeed incremented by 5.

Back to Angular

We are now ready to return to this example:

angular.module('myApp').controller('MyController', [function() {
  var self = this;
  self.myVar = 42;
  self.updateVar = function() {
    self.myVar = 23;
  };
}]);

Imagine that this controller was written like this:

angular.module('myApp').controller('MyController', [function() {
  this.myVar = 42;
  this.updateVar = function() {
    this.myVar = 23;
  };
}]);

If we had a variable called ctrl that pointed at an instance of MyController then if we called ctrl.updateVar(), we would expect it to change the value of ctrl.myVar from 42 to 23. Indeed, in many cases that is exactly what would happen. But, now, imagine that we did something like this:

var action = ctrl.updateVar;
var person = {
};
action.call( person );
console.log(ctrl.myVar); // prints 42
console.log(person.myVar); // prints 23

In this situation, we have invoked updateVar() with an explicit binding of this that did not match the instance of MyController and thus that call would NOT update the controller's instance of myVar! Instead, as the example indicates, a new property called myVar would be created on the person object since this was set to be pointing at person and not the controller.

While the code above is contrived, it is all too easy to recreate it in a web application. All that has to happen is that the function is registered as a callback on code that explicitly sets the execution context by invoking call(), apply(), or bind(). At that point, all bets are off... or are they?

This is where the var self = this; trick comes to the rescue. Recall that writing the controller to use the this variable was just an example, we actually implemented the controller like this:

angular.module('myApp').controller('MyController', [function() {
  var self = this;
  self.myVar = 42;
  self.updateVar = function() {
    self.myVar = 23;
  };
}]);

In this instance, self captures the value of this when the controller is first created. The value of self is then captured by updateVar() when it is defined via the magic of closures. Then, no matter how we call updateVar(), the controller's myVar will be updated since updateVar() ignores this and instead uses self to update the correct property. So, if our controller is implemented using the var self = this; trick, our example code now behaves like this:

var action = ctrl.updateVar();
var person = {
};
action.call( person );
console.log(ctrl.myVar); // prints 23
console.log(person.myVar); // prints undefined

Even though person was set to be the execution context of the call to updateVar(), the person object is not updated by the call since updateVar() does not make use of this but rather references self via its closure.

And that's why it is recommended for all of your controllers to make use of the var self = this; trick to ensure that controller functions are always able to update controller variables.

Since the example above is hard to reconstruct and see in action, I have created a program with a similar structure that proves that the var self = this; trick works as advertised (i.e. prevents explicit binding from breaking the controller). Check out Example 5 to see this in action.

Let me know if you have any questions or comments!

Prof. Ken Anderson
February 6, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment