1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical prototype and scipt.aculo.us part 29 pot

6 97 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 6
Dung lượng 87,63 KB

Nội dung

Functions Can Have Their Own Methods Imagine I’ve got a function that adds numbers together: function sum() { var num = 0; for(vari=0;i<arguments.length; i++) num += arguments[i]; return num; } The sum function 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, result is set to 0—the result of calling sum with no arguments. In the second example, result is set to the sum function itself. In effect, we’re giving sum an 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 c by giving it a and b ahead of time—getting back a function that expects only c. Confused yet? What I just said is much easier to express in code: CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 159 function 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. Function#curry can accept any number of arguments. I could load two or three 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 sum function we defined earlier: CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP160 var 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 sum with a value of 10 as the first argu- ment. It doesn’t matter how many arguments we pass to the curried function, since we’ve defined sum in a way that works with any number of arguments. The curried function will simply add 10 to the beginning of the argument list and pass those arguments to the orig- inal function. We’ll explore the use cases for Function#curry in later chapters. But it’s important for you to be familiar with it in the context of the other Function instance 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 have setTimeout, 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 setTimeout function 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 clearTimeout function. In this example, the setTimeout call 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. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 161 So 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 setTimeout getting 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 setTimeout function is ugly. Prototype prefers to give functions instance methods named delay and defer. Function#delay Function#delay expects one argument: a number that specifies the number of seconds to 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 delay after 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 CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP162 Now 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 Function#defer is equal to Function#delay with a timeout of 0.01 seconds (the smallest 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 how defer affects 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 twoReminders function, 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 twoReminders function. The commonest use case for defer is to postpone costly operations: function respondToUserClick() { doSomethingCostly.defer(); // instead of doSomethingCostly(); $('foo').addClassName('selected'); } Here, it’s best to defer the call to doSomethingCostly until 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. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 163 Using Function#bind To talk about Prototype’s Function#bind is to delve into JavaScript’s confusing rules about scope. Scope is the meaning of the keyword this in a given context. Let’s look back at our Totaler example. 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#initialize runs within this scope, as do all other methods in the Totaler class. Therefore, the keyword this refers to the instance itself when we’re inside 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, this becomes 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 Totaler on the page, I might want to alias its updateTotal method 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 retotal on 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 this means until a function gets called. Here we run into trouble—retotal doesn’t know anything about the Totaler instance’s scope. Function#bind solves this problem. It expects one argument and returns a version of the function whose scope is “bound” to that argument. Here, we want this to refer to totaler, so let’s bind it to that scope to make the function truly portable: CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP164 . FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP160 var sumPlusTen = sum.curry(10); typeof sumPlusTen; //-> "function" sumPlusTen(5); //-> 15 sumPlusTen(); //-> 10 sumPlusTen(2, 3, 4);. to the user. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 163 Using Function#bind To talk about Prototype s Function#bind is to delve into JavaScript’s confusing. 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

Ngày đăng: 03/07/2014, 01:20