JavaScript sets value of the execution context "this"
at call time. Being a bless of flexibility, this is also a curse of wrong context.
There are numerous situations when a function must keep the context no matter how it runs, and reference same object.
Use cases/mistakes
As a working example, we take a Menu
constructor, which should accept an element and create a menu on it’s base.
Something like this:
function Menu(elem) { // ... } // Usage: var elem = document.getElementById('something') // a DOM element var menu = new Menu(elem)
Most often use cases (and sources of mistakes) are:
- setTimeout
-
When you setup
setTimeout
, you may want it to reference the object:function Menu(elem) { setTimeout(function() { *!* alert(this) // window, not menu! */!* }, 1000) } new Menu(document.createElement('div'))
But
this
referenceswindow
, becausesetTimeout
always executes the function inwindow
context.So, there is no way to call
Menu
methods in the code above.
- onclick
-
function Menu(elem) { elem.onclick = function() { *!* alert(this) // elem, not menu! */!* } }
An element event handler always setsthis
toelem
instead ofmenu
.Again, the reference to the object is lost.
- Private method / local function
-
A local function is often used as a private method.
function Menu(elem) { function privateMethod() { *!* alert(this) // window, not menu! */!* } // ... call private method privateMethod() } new Menu(document.createElement('div'))
A simple call toprivateMethod()
usesthis = window
.
Naturally, we need ways to mitigate the problem, so that our function can access the menu
object. And fortunately, there is more than one.
Binding with var self = this
First, we can store a reference to this
in the closure.
In the example below, this
is copied to a new variable self
. This variable is then used instead of this
.
function Menu(elem) { *!*var self = this*/!* setTimeout(function() { *!* alert(self) // object! (menu) */!* }, 1000) } new Menu(document.createElement('div'))
Works equally with event handlers, local functions etc. All we need is a closure to put var self = this
.
Early binding
We could use a helper function bind
which forces this
.
Below is an example of such function. It accepts a function func
and returns a wrapper which calls func
with this = fixThis
.
function bind(func, fixThis) { return function() { return func.apply(fixThis, arguments) } }
This time let’s take an event handler.
function Menu(elem) { elem.onclick = bind(function() { alert(this) // object! (menu) }, this) }
If this
is bound permanently to the handler, then it replaces the reference to the element. So, the element becomes inaccessible, right?
Of course not. In the example above, elem
can be retrieved from the closure.
elem.onclick = bind(function() { alert(elem) // element ;) }, this)
..But that may not work in other cases. For example, in the loop:
// ... for(var i=0; i<10; i++) { elem = ... elem.onclick = bind(function() { alert(this) }, this) }
The first option in this case is to fix the code to keep elem
. See the task Shooters in the loop for details.
The second option is when you are using a JavaScript framework to assign event handlers. Then the event
object comes to the resque with it’s currentTarget
property.
var handler = bind(function(e) { alert(e.currentTarget) }, this) // using jQuery here. Any other framework will do. $(elem).click(handler)
The role of the framework here is to provide e.currentTarget
in IE<9. It is not supported by default, but all modern frameworks “fix” the event object, so the approach works.
Function.prototype.bind
There is a native Function.prototype.bind method which does exactly the same, plus it allows to fix arguments.
It is supported by most modern browsers. For those which don’t support, we can emulate it:
Function.prototype.bind = Function.prototype.bind || function(fixThis) { var func = this return function() { return func.apply(fixThis, arguments) } }
Polluting native prototypes is generally not recommended, but modifying Function.prototype
is somewhat acceptable. It doesn’t affect iteration over objects and arrays, so side-effects are minimal.
The emulation above is not as powerful as a native implementation, because it doesn’t allow to provide arguments. See the Prototype framework for a full version. Anyway, it does what we need here.
The binding becomes even simpler now.
- setTimeout
-
When you setup
setTimeout
, you may want it to reference the object:function Menu(elem) { setTimeout(function() { alert(this) // object! (menu) }*!*.bind(this)*/!*, 1000) } new Menu(document.createElement('div'))
- onclick
-
function Menu(elem) { elem.onclick = function() { alert(this) // object! (menu) }*!*.bind(this)*/!* }
- Private method / local function
-
That’s a bit trickier, because you can’t call a method in FunctionDeclaration:
function Menu(elem) { function privateMethod() { alert(this) }.bind(this) // syntax error, can't call method in-place }
So need to use FunctionExpression:
function Menu(elem) { var privateMethod = function() { alert(this) }*!*.bind(this)*/!* // ... now all fine, "this" is fixed privateMethod() } new Menu(document.createElement('div'))
Comparison
Let’s compare bind
versus var self = this
approach. Green is where bind
wins:
bind
does not depend on closure. The binding occurs instantly and lasts forever. Can be used without closures at all.- You have to add
bind
for every function which needs the context. That’s longer and sometimes less elegant than a singlevar self = this
. - There is no additional variable
"self"
. Saves time on refactoring.
In fact, both ways find their use. Which one you use more often is up to your personal style.
Late binding
Late binding is a variation of bind
with slightly different behavior.
In short, it means “binding on call time”, instead of “immediate binding”.
Problems of early binding
For example, here’s a code of a novice, who knows nothing about late binding. The code is buggy.
It outputs “Menu” on click, but it should output “SuperMenu”. Try to find out why by the means of a piercing programmer’s look.
<!DOCTYLE HTML> <html> <body> <script> function bind(func, fixThis) { // using custom bind for simplicity return function() { return func.apply(fixThis, arguments) } } function Menu(elem) { this.sayHi = function() { alert('Menu') } elem.onclick = bind(this.sayHi, this) } function SuperMenu(elem) { Menu.apply(this, arguments) this.sayHi = function() { alert('SuperMenu') } } new SuperMenu(document.body) </script> Click me. I'm a SuperMenu! </body> </html>Open the code in new window
The code uses “All-in-one constructor” pattern to inherit SuperMenu
from Menu
. It is described in All-in-one constructor pattern.
…
Did you try? I hope, you found the reason successfully.
Of course, the problem occurs, because onclick
uses this.sayHi
of Menu
, not that of SuperMenu
.
The late binding in action
To use late binding, we use bindLate
instead of bind
:
<!DOCTYLE HTML> <html> <body> <script> function bindLate(funcName, fixThis) { // instead of bind return function() { return fixThis[funcName].apply(fixThis, arguments) } } function Menu(elem) { this.sayHi = function() { alert('Menu') } elem.onclick = bindLate('sayHi', this) } function SuperMenu(elem) { Menu.apply(this, arguments) this.sayHi = function() { alert('SuperMenu') } } new SuperMenu(document.body) </script> Click me. I'm a SuperMenu! (now working) </body> </html>Open the code in new window
Now it works. The change is:
elem.onclick = bindLate('sayHi', this)
The wrapper created by bindLate
resolves the object method at calling time, so the bug is fixed.
Of course, bindLate
works for object methods only.
There are frameworks which have late binding built in. For example, in jQuery there is a function $.proxy
which can do both early and late binding depending on how it is called.
Summary
There are 3 binding methods:
- Save
this
in a local variable (var self = this
) and use it inside nested functions. - Bind a function to proper
this
withbind
- especially convenient when it’s inFunction.prototype
. - Use late binding for a method call if the method may change or doesn’t exist at the time of binding.
All methods have their pros and contras. Together they are indeed useful. Happy binding