Earlier, we saw that copying a reference to a method out of an object treats the object as a standard function, not associated with any particular object. It would be useful to be able to package up an object-method pair as a single function that can be passed around.
Here's how:
function bind(object, method) {
return function () {
return method.apply(object, arguments);
}
}
The above example depends upon several JavaScript features: lexical closures, anonymous first-class functions, the apply() method of a function, and the arguments variable. Let's try using it.
var obj = {
x: 3;
m: function (y) {
return this.x + y;
}
};
var objm = bind(obj, obj.m);
var result = objm(7); // result = 10
The bind(obj, obj.m) notation is a little bit clunky—particularly if obj were instead a compound expression. How can we make it simpler?
Recall that either dot-notation or bracket-notation can be used to look up object fields. If the user provides the name of a method to use, we can look it up in the object:
function bind(object, method) {
if (typeof(method) == 'string') {
method = object[method];
}
return function () {
return method.apply(object, arguments);
}
}
And now we can use it as follows.
var objm = bind(obj, 'm');
var result = objm(8); // result = 11
Here is the bind method defined in the prototype.js JavaScript library. What are the two main differences from our first bind function above?
Function.prototype.bind = function () {
var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift();
return function () {
return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
};
};
This function adds a new method to an object. If a method already existed by the same name, but with a different number of arguments, then the old method is called when the number of arguments doesn't match those of the new method. (Borrowed from http://ejohn.org/apps/learn/#89)
function addMethod (object, name, fn) {
// Save a reference to the old method
var old = object[name];
// Overwrite the method with our new one
object[name] = function () {
// Check the number of incoming arguments,
// compared to our overloaded function
if (fn.length == arguments.length)
// If there was a match, run the function
return fn.apply(this, arguments);
// Otherwise, fallback to the old method
else if (typeof old === "function")
return old.apply(this, arguments);
};
}
Try it out:
var obj = {
f: function (x) { return x+1; }
}
addMethod(obj, 'f', function (x, y) { return x * y; });
obj.f(7) == 8
&& obj.f(7, 3) == 21;
A continuation represents "the rest of" a computation. For example, when you call a function in C, the return address pushed onto the stack, together with the stack contents from the caller and its callers are an implicit continuation. In some languages, you can convert the implicit continuation into an explicit continuation function: for example, Scheme has the call-with-current-continuaion function (also known as call/cc).
JavaScript does not have call/cc. However, we can explictly write continuations as functions. This is known as continuation-passing form. For example, this program is written in standard form:
function add(x, y) {
return x + y;
}
var z = add(1, add(2, 3));
And here is a corresponding program in continuation-passing form:
function add(x, y, k) {
return k(x + y);
}
var z;
add(2, 3,
function (n) {
return add(1, n,
function (n) {
z = n;
}
);
}
);
We can picture the above program diagramatically:
add(2, 3) → add(1, ◊) &rarr z = ◊
While explicit continuations only obscure simple arithmetic, they can be quite useful for writing event-based programs, such as graphical user interfaces. In order to write programs that react to events, we need to suspend a computation, and then continue it when the relevant events occur.
One useful construct is the ability to wait for events from some number of sources, and proceed only when all sources have fired an event. This is known as a join continuation. If each source can only fire one event, this is equivalent to waiting for some number of events. We can write the following higher-order continuation adapter to transform a simple continuation into an n-way join continuation.
// Returns a continuation which, after it's called n times, invokes the continuation k. function join(n, k) { if (n <= 0) { // Call the continuation k immediately // (i.e., after the continuation we returned is called zero times) k(); return function () { } } else { // Call the continuation k after the continuation we return is called n times return function () { if (n > 0) { n--; if (n == 0) { k(); } } } } }
This program works correctly, without any locking, because JavaScript is a single-threaded language. This fact that JavaScript is single-threaded. means that we must use event-based programming to create a responsive user interface. Example usage:
var jk = join(3, function () { alert("done"); });
jk(); // nothing happens yet...
jk();
jk(); // alert("done") occurs now.
jk(); // no further effects