Context Binding in Javascript with Closure
Before we go on, don’t let anyone tell you JavaScript is not a programming language (if he tells you that, you should give him a hug because he sure needs one). It rightfully is. And a sneaky one.
We all know too well the untamed this
, known as the One Who doesn’t Bind.
For those of you who are still on a honeymoon with JavaScript, you might want to head here and read about what you’ll soon find out about this
. Or, on second thought, you might be better off turning back and returning at a later time when you need a therapy session.
Okay, you’re still here. Here is a rundown:
this
keyword mostly depends on how, or where the enclosing function is called.- You cannot set
this
keyword to something else during execution time. - To make the matter worse,
this
behaves differently in strict mode.
Example 1: Window object
// In the browsers, the window object is globalwindow.foo = "bar";
console.log(this.foo === window.foo) // true
console.log(this === window); // true
Example 2: Function context
Most of the time, this is bound to whatever context the function is called:
// This is window/global scope
let a = (function() {
return this;
})()console.log(a === window); // true
Example 2: Function context in strict mode
In strict mode, this
is what it was set to when entering execution context. So in this case this
becomes undefined.
let a = (function() {
'use strict';
return this;
})()console.log(typeof a === 'undefined'); // true
So the only way to bind this to the surrounding context is by injecting it:
let a = (function(ctx) {
'use strict';
return ctx;
})(this);console.log(a === window); // true
Example 3: setting this
with bindbind
is a very useful method introduce in ES5 for setting this to an arbitrary context. However, we all know it’s a mystery to many JavaScript users (including myself not long ago).
let context = {foo: 'bar'};
let a = (function() {
return this;
}).bind(context)();console.log(a === context); // true
The arrow function
In ES2015 (or ES6, what’s with the naming anyway), the arrow function was introduced. With a function declared using the arrow syntax, the this
keyword behaves lexically, or binds to whatever execution context is surrounding the function it is in. For instance,
// global/window context
let a = (() => {
return this;
})();console.log(a === window); // trueclass A {
constructor() {
this.foo = () => {
return this;
};
}
}let a = new A();// this is bound to the class instance
console.log(a.foo() === a); // true
This kind of promoted JavaScript’s anonymous functions to be on par with lambda elites like Haskell or Clojure (shhhhhh, here give me a hug, functional hackers). I also found that it sets the enigmatic this
on leash, for the the very last time. And for the first time in my JavaScript career, bind
has stopped haunting me.
Then today, I ran into something resembles this:
class Foo {
constructor(name) {
this.name = name;
this.bar({
printName: () => {
console.log(this.name);
}
});
} bar(obj) {
obj.printName();
}
}
This was tricky, at least for me. I was expecting this.name
to refer to Foo
’s instance’s name
. However, this obviously binds to an unnamed object:
{ printName: () => {
console.log(this.name);
}
}
Because it is the immediate context between the arrow function’s and the class’s (remember arrow functions do not create a new scope for this).
I wanted to use bind
, but then again I was inside a class definition, and (as far as I’m concerned) there’s no way to bind to the class instance. Seemed like the most straightforward way was to refactor:
class Foo {
constructor(name) {
this.name = name;
this.bar = this.printName;
}
printName() {
console.log(this.name);
}
}
Which looks pretty tidy and cute, but in my case is not possible or at least not straightforward.
Closure Function and Injection
Then I remembered what I once have forgotten. I didn’t have to pass an object if whatever I pass in returns one! In this case, I can pass in an anonymous arrow function that takes the context (the class instance) as an argument and refer to that instead of this
.
class Foo {
constructor(name) {
this.name = name;
this.bar(((foo) => {
return {
printName: () => {
console.log(foo.name);
}
};
})(this));
} bar(obj) {
obj.printName();
}
}
Now the bar
method could remain unchanged, and I could finally smuggle the outer context in by injecting it into an arrow function!
This technique is also great for closures in older JavaScript codebase in which arrow functions are not available too. Where you normally see people using hacks like varself = this;
var self = this;
var eventHandler = function() {
console.log(self.data);
}
Because you never know in what context eventHandler
will be executed, and this
isn’t reliable, attaching the desired context to self
and close it up in a function makes perfect sense. But if the first line was unintentionally removed and there’s already a variable named self
before it, you are opening your code to a runtime bug that won’t throw an error.
By using the approach discussed, we make the function all the more functional and portable, closing the context up by injecting it into the outer function that returns a closure around the desired call:
var eventHandler = (function(self) {
return function() {
console.log(self.data);
}
})(this);