Hello, I'm Nick Fitzgerald. This is my weblog. You can also check out my shared items from Reader and code on GitHub. Feel free to contact me about whatever.
May 20th, 2010
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.
On August 10th, 2010
On August 2nd, 2010
On July 23rd, 2010
On July 19th, 2010
On July 5th, 2010
On June 29th, 2010
On June 25th, 2010
On June 21st, 2010
On June 2nd, 2010
On May 20th, 2010
blog comments powered by Disqus