Method closure

From JASPA

Jump to: navigation, search

Contents

Introduction

A method closure is a function reference that remembers what object it belongs to. JavaScript and ActionScript 2 do not support such a thing natively, but ActionScript 3 now does and so developers are starting to take this concept for granted.

Example problem

Consider the problem in JavaScript

var Cat = function(){
    this.voice = "Miaow";
}
Cat.prototype.speak = function(){
    alert( this.voice );
}
 
var Sammy = new Cat;
Sammy.speak(); // alerts "Miaow"
var Callback = Sammy.speak;
Callback(); // alerts undefined

As soon as we've created a reference to the function member, we've severed it from its owner, so when we call the reference it is no longer being called on the object, it is being called within the global scope. There are three situations where a function reference may be created and cause this problem:

  1. Assigning a function to a variable (as above);
  2. Returning a function from a another function;
  3. Passing a function as an argument to another function.

The AS2 solution

AS2 developers are used to writing Delegate.create to get around this problem, especially common when passing a method as an event handler. This is fine except that with AS3 taking precedence this legacy behaviour is in danger of getting forgotten.

The JASPA solution

JavaScript suffers the same draw-back as AS2, and as JASPA generates regular JavaScript it cannot create any new native behaviour. However, it can save you having to worry about it - Every time you create a reference to an object method, the JASPA compiler automatically generates a call to the special jaspa.closure function.

Example solution

The assignment expression var Callback = Sammy.speak; from our example above would be re-written by the JASPA compiler as follows

var Callback = jaspa.closure( Sammy, Sammy.speak );

The jaspa.closure function is found in Core/Function.js and the source code for the function is as follows:

/**
 * Method closure making function.
 * This is called implicitly any time an object's method is passed as a reference
 * @param Object any object scope
 * @param Function any function object
 * @return Function wrapper function that remembers the instance it should be called on
 */
jaspa.closure = function ( b, f ){
	if( typeof f !== 'function' ){
		throw new Error('Closure argument is not a function');
	}
	var c = function(){
		return f.apply( b, arguments );
	}
	c.apply = function( b, a ){
		return f.apply( b, a );
	}
	c.call = function( b ){
		var a = [];
		for( var i = 1; i < arguments.length; i++ ){
			a.push( arguments[i] );
		}
		return f.apply( b, a );
	}
	return c;
}


Known problems

There are currently two known problems with the JASPA solution. Solutions to both of the following are in the pipeline:

Microsoft Internet Explorer

MSIE does not provide the standard Function interface for built-in methods. That is to say that native functions such as window.alert are not of type "function", but of type "object". This means that apply and call cannot be used on native functions.

Although a function could be invoked as objectRef[funcName](), the passing of arbitrary arguments is still a problem.

Function comparisons

Because jaspa.closure generates a new function object with each call, it is not always possible to compare two identical closures. This is currently a problem for procedures like removeEventListener which rely on comparing method closures.

The current development version of the JASPA core under CVS proposes the following addition:

// [...]
// Addition of toString/valueOf function generates a unique identifier for the object/function pairing
c.valueOf  = 
c.toString = function(){
	if( b.$closureId == null ){
		b.$closureId = ++jaspa.closureId;
	}
	if( f.$closureId == null ){
		f.$closureId = ++jaspa.closureId;
	}
	return '[Method Closure #'+b.$closureId.toString(16)+':'+f.$closureId.toString(16)+']';
}
// [...]

Using the valueOf method would allow accurate comparisons between duplicate instances as the following JavaScript shell session shows:

js> var a = jaspa.closure(this,print);
js> var b = jaspa.closure(this,print); 
js> a;
[Method Closure #1:2]
js> b;
[Method Closure #1:2]
js> a === b;
false
js> a.valueOf() === b.valueOf();
true
js> quit();

This is preferable to maintaining a registry of instances, such an approach would waste memory as closures would persist indefinitely.

See also

External links

Personal tools