Function Scope and Binding Event Handlers

Functions in JavaScript use the scope that is in effect when they are defined and not the scope in effect when they are invoked. You may find this confusing because the variables you define inside the function have their scope determined when the function is invoked. In fact while these appear to be different they are in fact the same thing because the variables defined inside the function also have their scope determined at the time the variables are defined which just happens to correspond to when the function was invoked.

Where this tends to catch people out is with those functions that are actually defined prior to your code starting to run and where the scope at the time that it looks like you are defining them is not the same as the scope that actually existed when JavaScript itself defined them. There are two examples of this which commonly catch people out. In both cases we are defining a function to be invoked later but where the function we are using to attach the code has been previously defined and so is not the current scope.

The first of these is event listeners/handlers. JavaScript itself defines all of these on the actual DOM elements themselves as the DOM is generated from the HTML. This occurs before you have the opportunity to actually attach your own code to be run when that event is triggered. So the code you attach to the event will run in the scope defined by the DOM element that triggered it and not the scope at the time you added the code.

The first of these is functions such as setTimeout and setInterval where JavaScript defines these in global scope before it starts processing your code. The function you attach inside of these to be run at a later time will therefore run in global scope and not in the scope that existed at the time you called the setTimeout or setInterval.

JavaScript provides three ways to override this behaviour and so run a function in a different scope from that in which it was defined. You can change the scope at the time that you invoke the function by using call() or apply() or you can change the scope at the time you define the function by using bind(). In each of these cases the function will run in the scope you specify rather than in the scope in which the function was originally defined.

The JavaScript variable 'this' points to the object that is currently in scope. We can make use of this to change the scope that the functions we are defining to run later to have them run in the current scope. For example, instead of:

setTimeout(functionname, 1000);

which runs functionname() in global scope, we could call:

setTimeout(this.functionname.bind(this), 1000);

which runs functionname in the scope that we are in at the time of invoking the setTimeout instead.

Similarly with event handlers, by default JavaScript will treat the code you attach to an event handler as belonging to the element that triggered the event. For example:

button.onclick = function() {myfunc(parm1,parm2);};

When the event handler actually runs the myfunc function in that example code the parameters passed to the function will be the values in parm1 and parm2 at the time the function runs and any use of this within the function will refer to the DOM element that triggered the event. The function has no access whatsoever to the button object that was responsible for the event being triggered in the first place.

If we need myfunc to have access to the button object when it runs we need to first make it a method of that object and then bind it to that object so that this will continue to refer to button when the method runs.

button.onclick = this.myfunc.bind(this, parm1, parm2);

With our event handler being defined this way myfunc() is a method of the button object and will continue to have access to any properties of that object when the event is triggered. In addition parm1 and parm2 are also passed with the values they had at the time of executing the bind rather than the time the event is triggered.

 

This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow
Donate