}); } }); var player = new Player("Johnny", "Generic"); Object.toJSON(player); //-> '{ "firstName": "Johnny", "lastName": "Generic", "points": 0 }' var qb = new Quarterback("Andrew", "Dupont"); Object.toJSON(qb); //-> '{ "position": "QB", "firstName": "Andrew", //-> "lastName": "Dupont", "points": 0 }' You need only define a toJSON method to tell your object how to encode itself into JSON. The preceding example illustrates one of JSON’s drawbacks: it isn’t a “lossless” for- mat. Since these classes contain functions, there’s no way they can be converted to JSON and come back wholly intact. But this is the minimum amount of information we need to restore the class as it existed originally. JSON can’t do this automatically, but we can do it manually without much effort. Using Object Methods Prototype defines quite a few methods on Object, the generic constructor and patriarch of the JavaScript family. Unlike the String methods just covered, these aren’t instance methods—they’re attached to Object itself. Type Sniffing with Object.isX The hasty conception and standardization of JavaScript in the 1990s left a few gaping holes in the language. One of the biggest holes is the typeof operator; in theory it gives us useful information about an item, but in practice it acts like Captain Obvious. Sure, it handles the simple cases just fine: typeof "syzygy"; //-> 'string' typeof 37; //-> 'number' typeof false; //-> 'boolean' CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS184 But when it’s asked to do more complex checks, it falls flat on its face: typeof [1, 2, 98]; //-> 'object' typeof new Date(); //-> 'object' typeof new Error('OMG'); //-> 'object' Array s, Dates, and Errors are objects, of course, but so are all non-primitives. Imagine if you asked me, “Do you know what time it is?” and I responded only with “Yes.” My answer is both narrowly correct and completely useless. The one other value returned by typeof is function, but that applies both to functions and regular expressions: typeof $; //-> 'function' typeof /ba(?:r|z)/; //-> 'function' In other words, typeof arbitrarily singles out functions, even though they’re instances of Object just like Array, Date, and Error types. And the fact that RegExp is a function (if we’re being technical about it) just makes it harder to distinguish them from true functions—what a useless taxonomy. Prototype includes the type checking that the language left out. In a dynamic, browser-based scripting environment, some checks happen again and again. Prototype defines a handful of methods that test for certain data types: they all accept one argu- ment and return either true or false. The Object.isString, Object.isNumber, Object.isFunction Methods The three simplest of these functions behave exactly like their typeof equivalents: Object.isString("foo"); //-> true Object.isNumber("foo"); //-> false Object.isFunction($); //-> true; Object.isNumber(4); //-> true Object.isNumber(3 + 9); //-> true Object.isNumber("3" + 9); //-> false Skeptics may wonder why these methods are defined at all—if the behavior is identi- cal to typeof, why not use typeof instead? These methods are slightly shorter to type than the alternative, but they exist mostly so that you’ll get out of the habit of using typeof.If that bothers you, feel free to ignore them for philosophical reasons. CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 185 The Object.isUndefined Method Here’s another nugget of JavaScript trivia, in case you didn’t know: null and undefined are two separate values. If you asked to borrow a dollar, null would be a firm, loud reply of “No!”; undefined would be a distant stare, as if I hadn’t heard your question. In other words, undefined means that a value has not been set for a variable, and null means that the value has been explicitly set to nothing. For example, if I omit a named argument from a function, it will be set to undefined: function amigos(first, second, third) { alert(typeof third); } amigos("Lucky", "Ned", "Dusty"); // alerts "string" amigos("Lucky", "Ned"); // alerts "undefined" JavaScript treats these two properties differently just to screw with our heads: typeof undefined; // "undefined" typeof null; // "object" Huh? Why is null an object? Do you mean for us to scream? Fortunately, you can test for null by using a strict equivalence check (using three equals signs, rather than two). For example, consider Element#readAttribute, which returns null if the specified attribute does not exist: if ($('lastname').readAttribute('required') === null) alert("Last name not required."); On the other hand, the canonical way to check for undefined is to use typeof.We cannot allow that, however. Instead, we’ll use Object.isUndefined: function amigos(first, second, third) { alert(Object.isUndefined(third)); } The Object.isArray, Object.isHash, Object.isElement Methods The three remaining type-checking methods test for arrays, hashes (instances of Proto- type’s Hash class), and DOM element nodes. Each of these would respond to typeof with “object,” but this is unacceptable for arrays and DOM nodes in particular, since they’re among the most commonly used objects in the browser environment. CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS186 var amigos = ["Lucky", "Ned", "Dusty"]; typeof amigos; //-> 'object' Object.isArray(amigos); //-> true var villain = $('el_guapo'); typeof villain; //-> 'object' Object.isElement(villain); //-> true var partyFavors = $H({ cake: 1, amigos: 3, pinatas: "plethora" }); typeof partyFavors; //-> 'object' Object.isHash(partyFavors); //-> true Using Type-Checking Methods in Your Own Functions JavaScript’s weak typing is a boon to API developers, since it allows for functions that can take many different combinations of arguments. Consider Prototype’s own $: it can accept any number of arguments, each of which can be either a string or an element. Let’s look at the source code for $: function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } The first if statement handles the case where there is more than one argument: $ loops through the arguments, cleverly calls itself on each one, and then places each result into an array, returning that array. The final three lines deal with the common case: element is either a DOM element node or a string, so the function sniffs for strings and calls document.getElementById to convert them into elements. Thus, by the last line, element is guaranteed to be a true DOM node. CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 187 Less dynamic languages like Java would require that we define $ twice: once for pass- ing it a string and once for passing it an element. (Passing an indeterminate number of arguments wouldn’t work at all.) JavaScript, on the other hand, lets us write one function for all these use cases; we have the tools to inspect these arguments ourselves. In the case of $, performing a simple type check within the function saves the devel- oper’s time: she can write functions that accept strings and DOM nodes indifferently. Calling $ within those functions will ensure that the end result is an element. Think how much time you could save by automating the annoying type coercions that your functions demand? Prototype’s type-checking methods allow your code to read your mind just a bit better. Using Array Methods Way back in Chapter 3, I covered Enumerable and the methods it adds to collections. Arrays receive these methods, of course, but they also receive a handful of methods tailored specifically for arrays. The reverse and clear Methods The first two methods document themselves. Array#reverse inverts the order of an array; Array#clear removes all of an array’s items. Array#reverse returns a new array by default, but takes an optional Boolean argu- ment to reverse the original array: var presidents = ["Washington", "Adams", "Jefferson", "Madison", "Monroe"]; presidents.reverse(); //-> ["Monroe", "Madison", "Jefferson", "Adams", "Washington"] presidents; //-> ["Washington", "Adams", "Jefferson", "Madison", "Monroe"]; presidents.reverse(true); //-> ["Monroe", "Madison", "Jefferson", "Adams", "Washington"] presidents; //-> ["Monroe", "Madison", "Jefferson", "Adams", "Washington"] CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS188 For obvious reasons, Array#clear always acts on the original array: presidents.clear(); presidents; //-> [] The uniq and without Methods Two other methods winnow the contents of the array. Array#uniq takes its behavior and its odd name from the corresponding Ruby method—it returns an array without any duplicate values: var presidents = ["Washington", "Adams", "Jefferson", "Madison", "Monroe", "Adams"]; presidents.uniq(); //-> ["Monroe", "Madison", "Jefferson", "Adams", "Washington"] [1, 2, 3, 3, 2, 3, 1, 3, 2].uniq(); //-> [1, 2, 3] Array#without accepts any number of arguments and returns a new array without the specified values: var presidents = ["Washington", "Adams", "Jefferson", "Madison", "Monroe", "Adams"]; presidents.without("Adams", "Monroe"); //-> ["Washington", "Jefferson", "Madison"]; Summary Now that I look back on it, I realize that this chapter hasn’t been too embarrassing for either of us. I managed to fill in a few of the cracks I had skipped over in previous chap- ters, and you got a broader look at the some of the APIs Prototype provides. Part 2 of this book will focus on script.aculo.us and its robust effects library and set of UI controls. We’ll revisit everything covered in this chapter as we explore script.aculo .us through hands-on examples. CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 189 . will focus on script .aculo. us and its robust effects library and set of UI controls. We’ll revisit everything covered in this chapter as we explore script .aculo .us through hands-on examples. CHAPTER. of us. I managed to fill in a few of the cracks I had skipped over in previous chap- ters, and you got a broader look at the some of the APIs Prototype provides. Part 2 of this book will focus. Object just like Array, Date, and Error types. And the fact that RegExp is a function (if we’re being technical about it) just makes it harder to distinguish them from true functions—what a useless