Passing variables to referenced functions

Source: http://www.howtocreate.co.uk/referencedvariables.html

The problem

This problem is encountered with event handlers and in other situations, such as when adding methods to an object. Most Web authors first encounter it with event handlers, so that is what this article will concentrate on, but you should be aware that it also applies to those other situations.

In JavaScript, whether using traditional event handlers, or DOM events, event handlers are attached to their relevant object as a reference to the function. In standards compliant browsers, when the event fires, the function is executed as a method of the object, and passed a single parameter; the event object. In Internet Explorer, it is not passed any parameters at all:

function myhandler(e) {
  //...
}
mydiv.onclick = myhandler;
mydiv2.addEventListener( 'click', myhandler, false );

There are many occasions when it is desirable to pass a local variable to the handler function. An example would be when looping through a list of elements, and assigning the same event handler to each of them, but needing to pass a unique identifier to each handler so it can always reference the index of the element within a collection. You may also want to pass an object, not just an index. Perhaps this would be a reference to another element that is relevant to the handler.

The obvious desire is to try something like this:

mydiv.onclick = myhandler(i,myobject);

However, this does not work. What it will do is immediately execute the handler as a normal function, passing it the variables. Then whatever the function returns, even if that is a string, or perhaps nothing at all, is assigned as a property of the element with the name of the event handler. Obviously this will not work correctly as an event handler.

There are three main ways to overcome the problem. The first two are the most common, but have limitations. The third is one of the most complete solutions, but is not very common. The demonstrations are all prepared assuming the following example algorithm:

function myfunction() {
  var paras = document.getElementsByTagName('p');
  var spans = document.getElementsByTagName('span');
  for( var i = 0; i < paras.length; i++ ) {
	paras[i].onclick = function (e) {
	  //assume that here, we want to know
	  // - what the value of i was for this element
	  // - the span that corresponds to spans[i]
	};
  }
}
//...
myfunction();

In that example, the event handler is executed in the scope of the outer function, so paras, spans, and i are all preserved. However, they will have the values that they have in that scope at the time the handler is executed, not the values they had at the time the assignment was made. This means that (except in extremely rare cases where the event handler is executed before the end of the loop is reached), i will hold the value of paras.length, and not the value we need.

The solutions are:

  1. Using the Function constructor
  2. Assigning variables as properties of the element
  3. Using scope to remember the instances of the local variables

Using the Function constructor

This is the most limited and problematic approach, but it is the most commonly used - typically because the author was not aware of the alternatives or their advantages. The idea is to write the function code as a string, with the local variables added to the string. When the event handler runs, it is evaluated in the current scope (not the scope it was created in). The problems with this approach are numerous: * It has very poor performance. This comes from the conversion from string to executed code (similar to eval), and the creation of multiple functions that cannot be optimised by the JavaScript engine into a single stored function. * The local scope variables are lost. In this case, that means that the variable spans no longer exists. * It can only pass literals (strings, numbers, booleans, regular expression patterns, null), and not objects or arrays directly into the code. They can be referenced by name but only if they are available in the global scope, or the scope that the event handler will be executed in.

This means that although it is possible to pass i into the function code, spans will need to be made global, and the elements in it referred to using the index i as part of the function code:

	  var spans;
	  function myfunction() {
		var paras = document.getElementsByTagName('p');
		spans = document.getElementsByTagName('span');
		for( var i = 0; i < paras.length; i++ ) {
		  paras[i].onclick = new Function(
			'var a = '+i+', b;\n'+
			'b = spans[a];\n'+
			'... etc.'
		  );
		}
	  }
	  //...
	  myfunction();

Note that the objects returned by getElementsByTagName are dynamic, so if the number of spans changes between creating the event handler and running it, the spans object will be updated, and the index will not work.

Assigning variables as properties of the element

The idea for this approach is to store all the variables that will be needed as properties of the element that detects the event. Then when the function is executed, it can look for these properties on the element, referenced using the 'this' keyword.

This approach works well in most situations, but can have problems. Say for example that you want to use the same function multiple times, attaching it as a handler on the same element for multiple events, where each one expects different variables to be available. It is possible to work around this, but it can get messy. If using this, it is important to make certain that the chosen variable names will never be used by any browser as part of the DOM properties for that element. This also adds weight to the element objects, and is not the cleanest approach, but it does work:

	  function myfunction() {
		var paras = document.getElementsByTagName('p');
		var spans = document.getElementsByTagName('span');
		for( var i = 0; i < paras.length; i++ ) {
		  paras[i].originalindex = i;
		  paras[i].relatedspan = spans[i];
		  paras[i].onclick = function (e) {
			var a ##  this.originalindex, b  this.relatedspan;
			... etc.
		  };
		}
	  }
	  //...
	  myfunction();

Since this executes the event handler in the local scope, it would also be possible to reference the span using:

	  b = spans[this.originalindex];

Note that again, the objects returned by getElementsByTagName are dynamic, so if the number of spans changes between creating the event handler and running it, the spans object will be updated, and the index will not work.

Using scope to remember the instances of the local variables

This is the most complete and advanced approach, and solves all of the problems created by the other techniques. The idea is to draw on the initial mistake of executing a function, and use that to our advantage. Make it run a function, passing it the parameters. That function returns another function that exists inside it, within its local scope. This becomes the event handler when it is assigned, and is executed inside the local scope of the outer function, with the local variables that were passed to it preserved within that scope:

	  function scopepreserver(a,b) {
		return function () {
		  //do something with a and b
		  ... etc.
		};
	  }
	  function myfunction() {
		var paras = document.getElementsByTagName('p');
		var spans = document.getElementsByTagName('span');
		for( var i = 0; i < paras.length; i++ ) {
		  paras[i].onclick = scopepreserver(i,spans[i]);
		}
	  }
	  //...
	  myfunction();

The main problem with this approach is its lack of understanding, due to the number of authors who are not aware of it, and may be confused by what the code is attempting to do. You can always leave a comment in your code pointing them to this article. If you have trouble understanding it yourself, I suggest you see the function scope part of my JavaScript tutorial, where I describe how scopes like this work.

There is also the point that the handler function and the scope relating to it are preserved indefinitely and can use up memory. However, this is no worse than the other alternatives, which can also hold on to large numbers of variables indefinitely. You can always remove the handlers once you are finished with them, which will allow the garbage collector to free up the memory they used.

If you do not want to use an external function, it is also possible to define the scope preserving function inline, and execute it immediately. This has the added advantage that it preserves the scope of the main function (myfunction in the examples) as well as the inner scopes, but has the disadvantage that it becomes less easy for others to understand:

	  function myfunction() {
		var paras = document.getElementsByTagName('p');
		var spans = document.getElementsByTagName('span');
		for( var i = 0; i < paras.length; i++ ) {
		  paras[i].onclick = (function (a,b) {
			return function () {
			  //do something with a and b
			  ... etc.
			};
		  })(i,spans[i]);
		}
	  }
	  //...
	  myfunction();