function alertThreeThingsa, b, c {alerta; alertb; alertc; } var alertTwoThings = alertThreeThings.curry“alerted first”; alertTwoThings"foo", "bar"; // alerts "alerted first" // alerts "f
Trang 1Functions Can Have Their Own Methods
Imagine I’ve got a function that adds numbers together:
function sum() {
var num = 0;
for (var i = 0; i < arguments.length; i++)
num += arguments[i];
return num;
}
The sumfunction will add its arguments together and return a number If no
argu-ments are given, it will return 0
We know the syntax for calling this method:
var result = sum();
To call this function, we append empty parentheses If we take the parentheses off,
though, we’re no longer calling the function—we’re just referring to it:
var result = sum;
Be sure you understand the difference In the first example, resultis set to 0—the
result of calling sumwith no arguments In the second example, resultis set to the sum
function itself In effect, we’re giving suman alias
var result = sum;
result(2, 3, 4); //-> 9
We’re going to look at a few instance methods of Function At first, these may seem
like magic tricks, but they’ll become more intuitive the more you use them
Using Function#curry
Partial application (or currying) is a useful technique in languages where functions are
first-class objects It’s the process by which you “preload” a number of arguments into a
function In other words, I could “curry” a function that expects parameters a,b, and cby
giving it aand bahead of time—getting back a function that expects only c
Confused yet? What I just said is much easier to express in code:
Trang 2function alertThreeThings(a, b, c) {
alert(a);
alert(b);
alert(c);
}
var alertTwoThings = alertThreeThings.curry(“alerted first”);
alertTwoThings("foo", "bar");
// alerts "alerted first"
// alerts "foo"
// alerts "bar"
We’ve just defined a function that, if we were to write it out ourselves, would behave
like this:
function alertTwoThings(b, c) {
return alertThreeThings("alerted first", b, c);
}
But by using curry, we’re able to express this more concisely and flexibly
argu-ments into alertThreeThings:
var alertOneThing = alertThreeThings.curry("alerted first",
"alerted second");
alertOneThing("foo");
// alerts "alerted first"
// alerts "alerted second"
// alerts "foo"
var alertZeroThings = alertThreeThings.curry("alerted first",
"alerted second", "alerted third");
alertZeroThings();
// alerts "alerted first"
// alerts "alerted second"
// alerts "alerted third"
Let’s look at a less-contrived example We can curry the sumfunction we defined earlier:
Trang 3var sumPlusTen = sum.curry(10);
typeof sumPlusTen; //-> "function"
sumPlusTen(5); //-> 15
sumPlusTen(); //-> 10
sumPlusTen(2, 3, 4); //-> 19
Each of these examples is equivalent to calling sumwith a value of 10as the first
argu-ment It doesn’t matter how many arguments we pass to the curried function, since we’ve
defined sumin a way that works with any number of arguments The curried function will
simply add 10to the beginning of the argument list and pass those arguments to the
orig-inal function
We’ll explore the use cases for Function#curryin later chapters But it’s important for
you to be familiar with it in the context of the other Functioninstance methods we’re
about to cover
Using Function#delay and Function#defer
JavaScript has no formal “sleep” statement—no way to block all script execution for a
specified amount of time But it does havesetTimeout, a function that schedules code
to be run at a certain time in the future
function remind(message) {
alert("REMINDER:" + message);
}
setTimeout(function() { remind("Be sure to do that thing"); }, 1000);
The built-in setTimeoutfunction takes two arguments The first can be either a
func-tion or a string; if it’s a string, it’s assumed to be code, and will be evaluated (with eval) at
the proper time (It’s much better practice to pass a function.) The second argument
must be a number, in milliseconds, that tells the interpreter how long to wait before
try-ing to run the code we gave it It returns an integer that represents the timer’s internal ID;
if we want to unset the timer, we can pass that ID into the clearTimeoutfunction
In this example, the setTimeoutcall ensures that the function we’ve passed it will get
called at least 1000 ms (1 second) later Because browser JavaScript executes in a
single-threaded context, the interpreter might be busy 1 second later, so there’s no way of
knowing the exact moment But nothing will slip through: as soon as the interpreter is
idle again, it will look at the backlog of things that have been scheduled, running
any-thing that’s past due
Trang 4So maybe that’s a better way to think of setTimeout: it adds functions to a queue, along with a “do not run before” sticker that bears a timestamp In the preceding exam-ple, the timestamp is computed by adding 1000 ms to whatever the time was when we called setTimeout
As a thought experiment, then, imagine the second argument to setTimeoutgetting smaller and smaller What happens when it hits 0? Does such a thing trigger a wormhole through space-time? No, for our purposes, the result is far more mundane The inter-preter “defers” the function to run whenever it has a spare moment
Prototype thinks the setTimeoutfunction is ugly Prototype prefers to give functions instance methods named delayand defer
Function#delay
wait (not milliseconds, unlike setTimeout)
function annoy() {
alert("HEY! You were supposed to do that THING!");
}
annoy.delay(5);
// alerts "HEY! You were supposed to do that THING!" roughly 5 seconds later
There are those who might say that this is a just a fancy way of calling setTimeout I
think it’s a less fancy way of calling setTimeout, if fanciness can be measured in number of characters typed
// equivalent statements
setTimeout(annoy, 5000);
annoy.delay(5);
The gains are more obvious when you’re calling a function that takes arguments Any arguments given to delayafter the first are passed along to the function itself:
function remind(message) {
alert("REMINDER:" + message);
}
remind.delay(5, "Be sure to do that thing.");
// alerts "REMINDER: Be sure to do that thing." roughly 5 seconds later
Trang 5Now compare the several ways to say the same thing:
// equivalent statements
setTimeout(function() { remind("Be sure to do that thing." }, 5000);
setTimeout(remind.curry("Be sure to do that thing."), 5000);
remind.delay(5, "Be sure to do that thing.");
We save a few keystrokes through clever use of Function#curry, but we save far more
by using Function#delay
Function#defer
practical timeout in a browser environment) To defer a function call is to say, “Don’t do
this now, but do it as soon as you’re not busy.”
To illustrate this concept, let’s see howdeferaffects execution order:
function remind(message) {
alert("REMINDER:" + message);
}
function twoReminders() {
remind.defer("Don't forget about this less important thing.");
remind("Don't forget about this _absolutely critical_ thing!");
}
twoReminders();
// alerts "Don't forget about this _absolutely critical_ thing!"
// alerts "Don't forget about this less important thing."
In the twoRemindersfunction, we make two calls to remind The first, a deferred call,
fires after the second More specifically, the second call fires immediately, and the first
call fires as soon as the interpreter exits the twoRemindersfunction
The commonest use case for deferis to postpone costly operations:
function respondToUserClick() {
doSomethingCostly.defer(); // instead of doSomethingCostly();
$('foo').addClassName('selected');
}
Here, it’s best to defer the call to doSomethingCostlyuntil after the function exits That
way, the visual feedback (adding a class name to an element) happens without delay,
making the application feel “snappier” to the user
Trang 6Using Function#bind
To talk about Prototype’s Function#bindis to delve into JavaScript’s confusing rules about scope Scope is the meaning of the keyword thisin a given context
Let’s look back at our Totalerexample In Chapter 1, we talked about how a scope is created whenever an object is instantiated When I call new Totaler, I create a new scope for the instance; Totaler#initializeruns within this scope, as do all other methods in the
any of these methods
var Totaler = Class.create({
initialize: function(element, totalElement, options) {
this.element = $(element);
this.totalElement = $(totalElement);
this.options = Object.extend({
selector: ".number"
}, options || {});
},
For this reason, thisbecomes internal shorthand for the instance itself
Try to take one of these methods out of context, though, and you’ll run into prob-lems If I declare a new Totaleron the page, I might want to alias its updateTotalmethod for convenience:
window.totaler = new Totaler('cities', 'population_total');
var retotal = totaler.updateTotal;
All I’ve done is hand a method reference to retotal I should be able to call retotalon its own later on, but if I try it I run into problems:
retotal();
//-> Error: this.element is undefined
In JavaScript, scope is bound to execution context The interpreter doesn’t decide what thismeans until a function gets called Here we run into trouble—retotaldoesn’t know anything about the Totalerinstance’s scope
the function whose scope is “bound” to that argument Here, we want thisto refer to