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

Practical prototype and scipt.aculo.us part 51 pot

6 163 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 88,23 KB

Nội dung

Prototype As a Platform Prototype’s features exemplify the functionality that distinguishes frameworks from libraries. It provides more than just shortcuts—it gives you new ways to structure your code. In this chapter, we’ll look at some of these tactics and patterns. We’ll move beyond an explanation of what the framework does and into higher-level strategies for solving problems. Some of these are specific code patterns to simplify common tasks; others make code more modular and adaptable. Using Code Patterns A script written atop Prototype has a particular style (to call it flair would perhaps be overindulgent). It’s peppered with the time-saving patterns and terse syntactic shortcuts that are Prototype’s trademark. I’m calling these code patterns, but please don’t treat them as copy-and-paste sec- tions of code. They’re more like recipes; use them as a guide, but feel free to modify an ingredient or two as you see fit. Staying DRY with Inheritance and Mixins Prototype’s class-based inheritance model lets you build a deep inheritance tree. Sub- classes can call all the methods of their parents, even those that have been overridden. Prototype itself uses inheritance with the Ajax classes. The simplest of the three, Ajax.Request, serves as a superclass for both Ajax.Updater and Ajax.PeriodicalUpdater. script.aculo.us uses inheritance even more liberally. For instance, all core effects inherit from an abstract class called Effect.Base. Any Prototype or script.aculo.us class can be subclassed by the user and customized. Inheritance is a simple solution for code sharing, but it isn’t always the best solution. Sometimes several classes need to share code but don’t lend themselves to any sort of hierarchical relationship. 297 CHAPTER 13 That’s where mixins come in. Mixins are sets of methods that can be added to any class, independent of any sort of inheritance. A class can have only one parent class— multiple inheritance is not supported—but it can have any number of mixins. Prototype uses one mixin that you’ll recognize instantly: Enumerable. Enumerable con- tains a host of methods that are designed for working with collections of things. In Prototype, mixins are simply ordinary objects: var Enumerable = { each: function() { /* */ }, findAll: function() { /* */ }, // and so on }; We can use mixins in two ways: • We can pass them to Class.create. The arguments we give to Class.create are sim- ply groups of methods that the class will implement. The order of the arguments is important; later methods override earlier methods. • We can use Class#addMethods to add these methods after initial declaration. var Foo = Class.create({ initialize: function() { console.log("Foo#Base called."); }, /* implement _each so we can use Enumerable */ _each: function(iterator) { $w('foo bar baz').each(iterator); } }); // mixing in Enumerable after declaration Foo.addMethods(Enumerable); // mixing in Enumerable at declaration time var Bar = Class.create(Enumerable,{ initialize: function() { console.log("Bar#Base called."); }, CHAPTER 13 ■ PROTOTYPE AS A PLATFORM298 /* implement _each so we can use Enumerable */ _each: function(iterator) { $w('foo bar baz').each(iterator); } }); The difference between a mixin and a class is simple: mixins can’t be instantiated. They’re morsels of code that are meaningless on their own but quite powerful when used in the right context. Enough about Enumerable. Let’s look at a couple of modules you can write yourself. Example 1: Setting Default Options Many classes you write will follow the argument pattern of Prototype/script.aculo.us: the last argument will be an object containing key/value pairs for configuration. Most classes’ initialize methods have a line of code like this: var Foo = Class.create({ initialize: function(element, options) { this.element = $(element); this.options = Object.extend({ duration: 1.0, color: '#fff', text: 'Saving ' }, options || {}); } }); Here we’re starting with a default set of options and using Object.extend to copy over them with whatever options the user has set. We can extract this pattern into one that’s both easier to grok and friendlier to inherit. First, let’s move the default options out of the constructor and into a more perma- nent location. We’ll declare a new “constant” on the Foo class; it won’t technically be a constant, but it’ll have capital letters, which will make it look important. That’s close enough. Foo.DEFAULT_OPTIONS = { duration: 1.0, color: '#fff', text: 'Saving ' }; CHAPTER 13 ■ PROTOTYPE AS A PLATFORM 299 Now, let’s create a mixin called Configurable. It’ll contain code for working with options. var Configurable = { setOptions: function(options) { // clone the defaults to get a fresh copy this.options = Object.clone(this.constructor.DEFAULT_OPTIONS); return Object.extend(this.options, options || {}); } }; To appreciate this code, you’ll need to remember two things. First, observe how we clone the default options. Since objects are passed by reference, we want to duplicate the object first, or else we’ll end up modifying the original object in place. And, as the impos- ing capital letters suggest, DEFAULT_OPTIONS is meant to be a constant. Second, remember that the constructor property always refers to an instance’s class. So an instance of Foo will have a constructor property that references Foo. This way we’re able to reference Foo.DEFAULT_OPTIONS without using Foo by name. Now we can simplify the code in our Foo class: var Foo = Class.create({ initialize: function(element, options) { this.element = $(element); this.setOptions(options); } }); Now, if you’ve been counting lines of code, you’ll have discovered that we wrote about eight lines in order to eliminate about two. So far we’re in the hole. But let’s take Configurable one step further by allowing default options to inherit: var Configurable = { setOptions: function(options) { this.options = {}; var constructor = this.constructor; if (constructor.superclass) { // build the inheritance chain var chain = [], klass = constructor; while (klass = klass.superclass) chain.push(klass); chain = chain.reverse(); CHAPTER 13 ■ PROTOTYPE AS A PLATFORM300 for(vari=0,len=chain.length; i < len; i++) Object.extend(this.options, klass.DEFAULT_OPTIONS || {}); } Object.extend(this.options, constructor.DEFAULT_OPTIONS); return Object.extend(this.options, options || {}); } }; OK, this one was a big change. Let’s walk through it: 1. First, we set this.options to an empty object. 2. We check to see whether our constructor has a superclass. (Remember that magi- cal superclass property I told you about? It has a purpose!) If it inherits from nothing, our task is simple—we extend the default options onto the empty object, we extend our custom options, and we’re done. 3. If there is a superclass, however, we must do something a bit more complex. In short, we trace the inheritance chain from superclass to superclass until we’ve col- lected all of the class’s ancestors, in order from nearest to furthest. This approach works no matter how long the inheritance chain is. We collect them by pushing each one into an array as we visit it. 4. When there are no more superclasses, we stop. Then we reverse the array so that the furthest ancestor is at the beginning. 5. Next, we loop through that array, checking for the existence of a DEFAULT_OPTIONS property. Any that exist get extended onto this.options, which started out as an empty object but is now accumulating options each time through the loop. 6. When we’re done with this loop, this.options has inherited all the ancestors’ default options. Now we copy over the default options of the current class, copy over our custom options, and we’re done. Still with me? Think about how cool this is: default options now inherit. I can instan- tiate Grandson and have it inherit all the default options of Son, Father, Grandfather, GreatGrandfather, and so on. If two classes in this chain have different defaults for an option, the “younger” class wins. You might balk at the amount of code we just wrote, but think of it as a trade-off. Set- ting options the old way is slightly ugly every time we do it. Setting them with the Configurable mixin is really ugly, but we need to write it only once! So much of br owser-based JavaScript involves capturing this ugliness and hiding it somewhere you’ll never look. Mixins are perfect for this task. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM 301 Example 2: Keeping Track of Instances Many of the classes we’ve written are meant to envelop one element in the DOM. This is the element that we typically pass in as the first argument—the element we call this.element. There should be an easy way to associate an element and the instance of a class that centers on said element. One strategy would be to add a property to the element itself: Widget.Foo = Class.create({ initialize: function(element, options) { this.element = $(element); // store this instance for later this.element._fooInstance = this; } }); This approach works, but at a cost: we’ve just introduced a memory leak into our application. Internet Explorer 6 has infamous problems with its JavaScript garbage col- lection (how it reclaims memory from stuff that’s no longer needed): it gets confused when there are circular references between a DOM node and a JavaScript object. The element property of my instance refers to a DOM node, and that node’s _fooInstance property refers back to the instance. So in Internet Explorer 6, neither of these objects will be garbage collected—even if the node gets removed from the document, and even if the page is reloaded. They’ll con- tinue to reside in memory until the browser is restarted. Memory leaks are insidious and can be very, very hard to sniff out. But we can avoid a great many of them if we follow a simple rule: only primitives should be stored as custom properties of DOM objects . This means strings, numbers, and Booleans are OK; they’re all passed by value, so there’s no chance of a circular reference. So we’ve settled that. But how else can we associate our instance to our element? O ne approach is to add a property to the class itself. What if we create a lookup table for Widget.Foo’s instances on Widget.Foo itself? Widget.Foo.instances = {}; Then we’ll store the instances as key/value pairs. The key can be the element’s ID— it’s unique to the page, after all. Widget.Foo = Class.create({ initialize: function(element, options) { this.element = $(element); Widget.Foo[element.id] = this; } }); CHAPTER 13 ■ PROTOTYPE AS A PLATFORM302 . effects inherit from an abstract class called Effect.Base. Any Prototype or script .aculo. us class can be subclassed by the user and customized. Inheritance is a simple solution for code sharing,. overridden. Prototype itself uses inheritance with the Ajax classes. The simplest of the three, Ajax.Request, serves as a superclass for both Ajax.Updater and Ajax.PeriodicalUpdater. script .aculo. us uses inheritance. modular and adaptable. Using Code Patterns A script written atop Prototype has a particular style (to call it flair would perhaps be overindulgent). It’s peppered with the time-saving patterns and

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