Javascript, "bind", and "this"
All javascript functions have access to an implicit
argument this
. By default, this
refers to the global
window
object, but when a function is bound to an object as a
method, this
refers to the object.
function testThis() { return this === window; } testThis(); // true var obj = { testThis: function () { return this === obj; } }; obj.testThis(); // true
However, it is possible to dynamically call functions with a new value bound
to this
by using the Function.prototype.call
and Function.prototype.apply
methods. Call
takes arguments
individually, while apply
takes them as an array.
function alertMsg(message) { return alert(this + message); } alertMessage.call("Hello, ", "World!"); // alerts "Hello, World!" alertMessage.apply("Goodbye, ", ["cruel world!"]) // alerts "Goodbye, cruel world!"
But, often it is helpful to permanently bind
the scope
of this
for a function. This technique was first pioneered
by Prototype.js. At its most basic,
the function bind
will take two arguments: the first is an object
that will be bound as this
for the function that is the second
argument.
// Useful for making the "arguments" object a true array and also for creating a // copy of an existing array. function toArray(obj) { return Array.prototype.slice.call(obj); } // Bind in its simplest form function bind(scope, fn) { return function () { return fn.apply(scope, toArray(arguments)); }; } // Examples var dog = { noise: "Ruff!" }; var truck = { noise: "Honk!" }; function makeNoise() { return this.noise; } makeNoise(); // undefined bind(dog, makeNoise)(); // "Ruff!" bind(truck, makeNoise)(); // "Honk!" var noise = "<Douglas Crockford puking in disgust>"; makeNoise(); // "<Douglas Crockford puking in disgust>"
It is also possible to write bind
such that it curries your
function as well. For a more detailed description of currying in Javascript,
check
out this article by Angus Croll.
// Bind w/ currying function bind(scope, fn /*, variadic args to curry */) { var args = Array.prototype.slice.call(arguments, 2); return function () { return fn.apply(scope, args.concat(toArray(arguments))); }; } // Bind w/ currying examples function convert(ratio, input) { return (ratio * input).toFixed(1) + " " + this.unit; }; var pounds = { unit : "lbs" }; var kilometers = { unit: "km" }; var kilosToPounds = bind(pounds, convert, 2.2); var milesToKilometers = bind(kilometers, convert, 1.62); kilosToPounds(2.5); // "5.5 lbs" milesToKilometers(13); // "21.1 km"
It is possible to implement somewhat Pythonic iterators using a combination
of bind
and Array.prototype.shift
. Iterators provide
a cleaner, more elegant API for looping over the items in an array than the
typical for-loop could.
// A specific iterator var item, iterZeroToNine = bind([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], Array.prototype.shift); // Log each of the integers in the range 0 - 9 while (item = iterZeroToNine()) { console.log(item); } // A generic iterator generator function iter(arr) { var items = toArray(arr); // Also makes a copy return bind(items, Array.prototype.shift); }
The synthesis of closures and bind
provides a powerful tool for
abstracting and hiding implementation details. Consider the following code,
which implements a rudimentary stack data type for DOM nodes.
function createDOMStack(selector) { var elts = toArray(document.querySelectorAll(selector)), pushToElts = bind(elts, Array.prototype.push); return { push: function (item) { if (item instanceof HTMLElement) pushToElts(item); else throw new Error("Can only push DOM elements to this stack!"); }, pop: bind(elts, Array.prototype.pop) }; }
Its also worth noting that a native version of bind
with currying will
be available as Function.prototype.bind
once ECMAScript
5 is widely implemented by the browser vendors. For more information see
the WebKit ticket,
and Mozilla
ticket.