Chapter 6. Types

The class system we've been studying is one half of a pair of systems that MooTools implements. The other half, the MooTools type system, is very similar to the class system, but it focuses more on data as values and the various forms that values take.

Unlike the native object implementation and classes we discussed in separate chapters, the native JavaScript type system and the MooTools type system will both be tackled in this chapter. The two type systems are deeply intertwined, and discussing them separately wouldn't really aid our understanding of their similarities and differences.

With that in mind, let's talk about types.

Values and Type Systems

At the heart of programming is the manipulation of data. Whether we're writing a spreadsheet program to process numbers, a complex algorithm to animate images on a web page, or a backend system to keep track of user information, we're still working with data and its representations.

In our programs, we express our data in terms of values. The following, for example, are all valid JavaScript values:

42
true
'hello'
function square(x){ return x * x; };
{name: 'mark'}

The first value is a number, the second is the boolean value true, the third, a string spelling the word "hello", the fourth, a function named square, and, finally, a simple object. While all of these values represent data, they don't represent the same kind of data. Each value can be differentiated from the others by the kind of data it represents.

We distinguish the values in terms of their types. We can use the JavaScript typeof operator to determine the type of a value:

console.log(typeof 42);             // 'number'
console.log(typeof true);           // 'boolean'
console.log(typeof 'hello'),        // 'string'
console.log(typeof function(){});   // 'function'
console.log(typeof {name: 'mark'}); // 'object'

The type of a value determines not only what kind of data it represents, but also what operations can be performed on it. Two Number values, for instance, can be added together to obtain their sum. However, you can't directly add a Number value to an Object value. In the same sense, you can't invoke a String or get the substring of a Function.

Note

We're going to adopt the ECMAScript specification's standard of using capitalized type names. Thus, when you see "Object," we are referring to the Object type, in contrast to "object," which could mean any object. In the same way, we have Function versus function, Number versus number, and String versus string.

The types that values can take, as well as the operations that can be performed on particular types, are determined by the language's type system. The type system also provides ways to determine the type of a value and ways to convert a value from one type to another.

JavaScript uses a type-by-specification system: the type of a particular value is defined directly by the language specification. The result of the application of the typeof operator to a value is governed by specific rules defined by the ECMAScript specification, and the interpreter uses these rules to determine the type of any value.

JavaScript's type system is, unfortunately, quirky. It might appear fine from the example above, but it's skewed at best when used in practical situations. To illustrate, let's use the typeof operator to determine the types of some other values:

console.log(typeof {name: 'mark'}); // 'object'
console.log(typeof [1, 2, 3]);      // 'object'
console.log(typeof new Date());     // 'object'

Here we have three values: a basic object, an array, and a date object. These three values have different structures and uses, so we'd expect them to be different types. However, this is not the case; all these values are considered to be of the Object type in JavaScript.

JavaScript, at its core, is an object-based language. Most values in JavaScript are objects—and even the values that are not objects—like strings and numbers—are turned into objects when needed (more on this later). Weirdly enough, JavaScript's type system thinks that since all values are objects, they should all be grouped under the same type: Object. So an array or a date object, for example, is of the Object type in JavaScript, even if it's a special kind of object.

This is technically "logical," of course: since arrays and dates are objects, their type would be Object. But while this is okay from a semantic point of view, it becomes totally useless—and, often, counterproductive—for practical purposes. For us to be able to properly manipulate values, we need to be able to differentiate between the values we're working with. It's okay to think of everything as an object, but we also need a way to determine what kind of object it is exactly.

JavaScript's type system doesn't help in this area. All objects in JavaScript are of the Object type. The only exception is functions, which JavaScript distinguishes as the Function type, but that's just because these objects have the unique ability to be invoked. Any other object—regardless of its structure, use, or mode of creation—is of the Object type.

There is, however, an alternative. In the previous chapter we saw how objects are created using classes. We learned that a class is a special language construct that serves as a blueprint for creating objects by defining the structure of these objects in terms of properties and methods. When we instantiate a class, we create a new object that's structured according to the definition we put in our class.

Classes can actually be used as an alternative type system: a type-by-class system. Classes define the structure of objects and what operations can be done on these objects, effectively differentiating objects in terms of structure and use. An instance of the Car class, for example, is a different kind of object when compared to an instance of the Animal class. This is analogous to how types are determined: a Car value (or object) is different from an Animal value and it's their types (or classes) that determine what kind of value they are and what operations can be performed on them.

In fact, this type-by-class system is used as the native type system in many classical languages, like Ruby. As with JavaScript, every value in Ruby is an object instance of a class. The class and the type of an object are therefore the same thing in Ruby, and classes themselves are treated as types.

Interestingly, JavaScript also has an internal facility for a type-by-class system. Though there is no native class construct in JavaScript, the term "class" is used internally by the language to signify the type of an object. All JavaScript objects have an internal class property, which is a string containing the name of the object's type. Unfortunately, this property is only used internally by the interpreter for its type-based operations, and is not actually used in the language-accessible type system, which makes it unusable for our current purpose. To really implement a proper type system, we must do it ourselves by extending the language.

An Alternative Type System

Let's recall what we know about JavaScript objects. We know that all objects are created using a constructor function and these inherit from the prototype properties of these constructors. Like classes, constructor functions and their prototypes define the structure of objects through properties and methods. Since every possible JavaScript value (with the exception of null and undefined) has its own constructor, constructors can be used to implement a type-by-class system in JavaScript. Native constructors like Object, Array, or Date would become "classes" in this system, and the instances of these constructors would be typed properly according to the particular constructor.

MooTools classes seem like good candidates to use in a type-by-class system, since they're just abstractions on the native constructor-prototype pair. We could simply turn native constructors like Object into classes and use the same type-by-class system implemented in languages like Ruby. Unfortunately, this is impossible to do because of how the MooTools class system is implemented.

To illustrate, let's say we want to create a new Animal class. To do this, we pass an object that defines the structure of the class as an argument to the Class constructor. The Class constructor then returns a new Animal class. This Animal class is actually an abstracted constructor function created internally and returned by the Class constructor. It is the job of the Class constructor to create the constructor function that will become the class object. Therefore, a particular constructor function can only be considered a class object if it was created using Class.

The problem is that constructors like Object or Array are built-in objects; they're implemented directly by the JavaScript engine using another language, usually C or C++, and their implementation details aren't available via the JavaScript interface. Because only the Class constructor can create the abstracted constructor function that would become our class object, we'll have to replace the native implementation of these built-in constructors with our own if we want to turn them into classes. In other words, to turn built-in constructors into classes, we have to destroy them first.

Because of this limitation, MooTools doesn't use classes to implement its type system. Instead, it introduces a new class-like construct called a type object, which is created using the Type constructor.

Like a class, a type object is an abstracted constructor function that has additional properties and methods that make it easier to manipulate the underlying prototype object. But unlike a class, the actual constructor function that becomes the type object isn't created inside the Type constructor. Rather, type objects are simply existing constructor functions that are transformed by the Type constructor into type objects. Any existing constructor can therefore be turned into a type object—which makes it possible to use built-in constructors without replacing them.

The native Array constructor, for example, is turned into a type object simply by passing it to the Type constructor. The Type constructor doesn't create a new constructor function to replace the native Array constructor. Instead, it turns the original Array constructor function into a type object by augmenting it with new properties and methods. Thus, the built-in Array constructor and the MooTools Array type object are one and the same. Nothing was replaced or destroyed. In fact, most built-in constructor functions are turned into type objects automatically when MooTools loads.

Type objects perform two main functions. First, a type object is a representation of the actual kind of value in terms of what makes it different from all other values. For example, the Array type object represents all values that are ordered lists of data, while the Function type object represents all values that are separate units of code that can be invoked.

Second, a type object also functions as a "class" (in the object-oriented sense of the word) for the particular type, which means that the structure and behavior of the values of that type are controlled by the type object. Because the type object is a constructor function, all instances of this type inherit from its prototype property. The type object can therefore be used to modify or augment the structure of all values under that type.

The MooTools type system abstracts part of the native type system in the same way that the class system abstracts the native constructor-prototype model. But unlike the class system, the MooTools type system is meant to be a direct replacement—not just a simple alternative. The easiest way to see the type system in action is by using the typeOf function:

console.log( typeOf(42) );              // 'number'
console.log( typeOf(true) );            // 'boolean'
console.log( typeOf('hello') );         // 'string'
console.log( typeOf(function (){}) );   // 'function'
console.log( typeOf({name: 'mark'}) );  // 'object'
console.log( typeOf([1, 2, 3]) );       // 'array'
console.log( typeOf(new Date()) );      // 'date'
console.log( typeOf(new Class()) );     // 'class'

The typeOf function mimics the native typeof operator in the MooTools type system. Like its native typeof counterpart, the typeOf function gives us the type of a value as a string. Unlike the typeof operator, though, the typeOf function knows how to properly differentiate among objects. You'll see that arrays, dates, and classes don't return 'object' as their type, but rather their types as defined by their type objects.

Note

typeof is a JavaScript operator, like the addition operator + or the instanceof operator, so you don't need parentheses when using it. The typeOf function, however, is a true function, so you have to invoke it using the invocation operator (aka, parentheses). Also, take note of the "camelCase" name: typeOf(), not typeof().

An important point to note is that since MooTools introduces a separate construct for its type system, classes are not used to determine the type of an object. The separation of classes and types means that objects created using classes will have both a class and a type:

// a class
var Person = new Class();

// an instance
var mark = new Person();

console.log(mark instanceof Person); // true
console.log(typeOf(mark));  // 'object'

In this example, mark isn't of the Person type but rather of the Object type, even if its class is Person. In the MooTools system, objects created from MooTools classes always have Object as their type and only objects created from the actual MooTools type constructors are given a different type.

While this is indeed a limitation, it's not such a big problem compared to the limitations of the native type system. Take, for instance, arrays and regular expressions: both of these are vastly different kinds of objects, but the native type system marks both as Object types—and in the case of regular expressions, they could even be typed as Functions in some implementations! We might not be able to distinguish among the class-defined object types with the MooTools type system, but at least we're able to tell whether an object is really a regular expression or a function.

Besides, the MooTools type system is able to distinguish among all native object types, which means that the only objects that will return 'object' when passed to the typeOf function are plain objects and objects created using classes. So it becomes much easier to guess what kind of object we're dealing with. Combined with our knowledge of the actual classes we use in our programs, the limitation of the class system becomes almost nonexistent.

Native Types and Values

Before we actually discuss the internals of the MooTools type system, we need to familiarize ourselves with the native types and the values they take. If you're one of those people who have already memorized the ECMAScript language specification, feel free to skip this part. But if you only know the basics of JavaScript types, keep on reading because much of what follows discusses the internals of the types themselves, rather than their basic use.

The ECMAScript specification defines six main types: Undefined, Null, Boolean, String, Number, and Object. We could further classify these types into three groups:

  • Existence Types are special types whose values represent the condition of not having values. Null and Undefined are both existence types.

  • Primitive Types (or "primitives" for short) represent the simplest immutable (or unchangeable) values. Strings, numbers, and booleans are all primitives.

  • Composite Types represent compound values whose structures and behavior can be changed. All objects (including functions) are composite types in JavaScript.

All JavaScript values are typed as one of these native types, and we'll examine these native types and values as well as their corresponding MooTools type objects in the following sections. We'll also learn the value a particular type takes when it's converted to another value through the process of type casting.

Null and Undefined

The Null and Undefined types are the only existence types in JavaScript. These types and their values are special because they don't represent data, but instead stand for the absence of data.

Note

The ECMAScript language specification categorizes the Null and Undefined types as primitive types. However, I put them in their own group since they behave differently from other primitive values.

The Null type has only one value, which is represented by the null keyword. The null value denotes the absence of a value: assigning null to a variable means that the variable does not have an actual value. Thus, the Null value is like a placeholder value in cases where there's no value to use.

Like the Null type, the Undefined type has only one value, which is represented by the undefined variable. The undefined value is used to represent the value of any variable that has been declared but not defined:

// declare a variable
var thing;

console.log(typeof thing); // 'undefined'
console.log(thing === undefined); // true

// define the value
thing = 1;

console.log(typeof thing); // 'number'
console.log(thing === undefined); // false

Remember that in Chapter 2 we learned that variable declaration and variable definition are executed by JavaScript as two distinct steps. When a variable is declared but not given a value, its value is automatically set to undefined. The undefined value is also used as the value of object members that don't exist, as well as the value of a function's formal parameters with no corresponding argument passed during invocation.

One important thing to consider with the Undefined type is that its only representation—the undefined variable—is a variable, not a keyword. This means that the actual value of the undefined variable can be changed:

// declare a variable
var thing;

// change the value of undefined
undefined = 'nooo!';

console.log(thing === undefined); // false

The ECMAScript committee's decision to make undefined a variable was an unfortunate choice that's been resolved in ECMAScript 5 by making the variable read-only. However, you should still be careful not to change the value of the undefined variable since ECMAScript 3 is still widely used, and doing so would create havoc in your programs.

The null and undefined values can be transformed into primitives through type casting—and we'll see how this is done in a later section. For now, let's just remember that the null value is cast into the number value 0, the string value 'null' and the boolean value false. Meanwhile, the undefined value is cast into the number value NaN, the string value 'undefined' and the boolean value false.

Null and Undefined also have the distinction of being the only native types with no constructor functions. Therefore, they have no corresponding MooTools type object. Any variable that has null or undefined for its value will have no properties or methods, and trying to access a member of a null or undefined value will result in an error.

One important thing to note here is that MooTools treats the Undefined value the same as the Null value, which means that typeOf(undefined) will return 'null' instead of 'undefined'. This is a simplification on the part of MooTools, since both values represent the same state of not having an actual value. Therefore, in order to check for undefined in MooTools, you'll have to use strict comparison, such as value === undefined.

Primitive Types

The three primitive types—Number, String, and Boolean—represent the simplest of all possible values in JavaScript. They're the most basic types of data in terms of structure, containing only actual values without any properties or methods.

Note

That last statement might be a bit surprising. In JavaScript, only objects have properties and methods, while primitives like strings and numbers don't—although they sometimes appear as if they do. We'll get around to that in a bit.

Primitive values are immutable, which means that they can't be modified. When a primitive value is stored in a variable or passed as an argument to a function, a new copy of the value is created—giving them their other name, value types.

Booleans

The simplest primitive type is the Boolean type, which has only two possible values: true and false; these are represented in code by the true and false literals. Booleans represent the truth value of an expression. For instance, 4 == 4 yields true because the number 4 is indeed equal to itself, while (2 + 2) == 5 yields false since the correct answer should be 4. When cast to number values, true and false yield 1 and 0, respectively, and when cast to strings, they become 'true' and 'false'.

Numbers

The Number type, on the other hand, represents numeric values. A number value is represented in code using number literals, which are simply numeric characters written directly in the JavaScript program. Also within the Number type are two special values represented by the variables Infinity and NaN.

The Infinity variable represents the value of numeric infinity—in other words, a really, really large number (mindblowing, in fact). The NaN, meanwhile, represents the special not-a-number value, which is the result of an impossible mathematical operation—like squaring a negative number. NaN also represents any non-numeric value cast into the Number type. You can check whether a number value is NaN using the built-in isNaN function, and whether a value is not Infinity using the isFinite function.

Note

Like undefined, Infinity and NaN are variables, so you'll also have to be careful not to overwrite their default values. These variables have also been declared as read-only in ECMAScript 5.

All number values are cast to the boolean true except for 0 and NaN, which are cast to the boolean false. When cast to strings, number literals are simply turned into their string literal representation—like 42 to '42'—while NaN and Infinity are cast to 'NaN' and 'Infinity'.

String

Finally, the String type represents values that are sequences of characters—or, to put it simply, string values are representations of textual data. Strings are created using string literals, which are pairs of single or double quotes (i.e., "or") surrounding one or more characters. Each character in a string is given a numeric index that starts with 0, and strings with no characters are called empty strings.

Empty strings are cast to the boolean value false, non-empty strings to true. Casting strings to number values is a little more complex. An empty string is always cast to the number value 0, while non-empty strings are checked first before being cast. If the characters in the string represent a valid number value, then the string is cast to the actual number value of the characters, otherwise the string is cast to the number value NaN. So the string '24.50' is cast to the number value 24.50, but the string 'hello' will be cast to NaN.

Primitives as Objects

A unique thing about JavaScript's primitive types is that they can behave like objects. To give you an example, let's take a look at how the substring of a string value can be retrieved:

console.log('Hello World!'.substring(0, 4)); // 'Hello'

Here, we have a string value "Hello World!" created through a literal. We then invoked the substring method, which takes two arguments—from and to—that represent the numeric indices for the beginning and the end of the substring. We passed 0 and 4, telling the substring method to take the first up to the fifth character in the string and return it. The result is a new string, 'Hello'.

Remember the object method called hasOwnProperty? We said that all objects inherit a hasOwnProperty method from Object.prototype, and strangely enough, even primitives have them:

console.log( typeOf('M!'.hasOwnProperty) ); // 'function'
console.log( typeOf((42).hasOwnProperty) ); // 'function'
console.log( typeOf(true.hasOwnProperty) ); // 'function'

So strings, numbers, and booleans all inherit the hasOwnProperty method. But inheriting properties and method from Object.prototype is a characteristic of objects, not primitives. Does this mean that strings, numbers, and booleans aren't really primitive values but are objects?

That's not the case, of course. Earlier in the chapter, we saw that the typeof operator returns the proper types for primitive values. If these values were objects, typeof would have returned 'object' and not 'string', 'number', and 'boolean'. This was clearly not the case.

But if they're truly primitive values, why do they behave like objects? The answer lies in JavaScript's use of wrapper objects. For each primitive type, JavaScript has a corresponding "object version" of that type, which is simply an object representation of a primitive value. Thus, we have three constructors that represent primitive types: String, Number, and Boolean.

The instances of these three constructors are objects, not primitive values, and we can confirm this using the native typeof operator:

console.log( typeof 'hello' ); // 'string'
console.log( typeof new String('hello') ); // 'object'

console.log( typeof 42 ); // 'number'
console.log( typeof new Number(42) ); // 'object'

console.log( typeof false ); // 'boolean'
console.log( typeof new Boolean(false) ); // 'object'

The name "wrapper object" comes from the use of these constructors. When the JavaScript interpreter sees a primitive value in an expression that requires an object, it creates a wrapper object that is used as an object representation of the primitive value. The following is what actually happens to the string example above:

console.log(new String('Hello World').substring(0, 4)); // 'Hello'

The string literal "Hello World" is first turned into a string object by passing the original value to the String constructor. The substring method of String.prototype is then invoked, and a new string primitive is returned as a result of the method call.

The process of turning primitives into objects is done automatically by the JavaScript interpreter, and the process is repeated for every operation. When the operation is done, the wrapper object is discarded and the original primitive value is restored. Since the wrapper object is created and destroyed for each property access operation, any property added to the wrapper object isn't persisted, and this preserves the immutability of the primitive value:

var str = 'Hello World';
str.type = 'greeting';

console.log(str.type); // undefined

Here we stored the string value "Hello World" to our variable str. When the second line is executed, a new string object wrapper is created to wrap the value of str, before adding a new property called type to the object. Because this wrapper object is destroyed right after the operation, accessing str.type on the last line produces undefined. Thus, the original string value remains unchanged.

You can, however, persist changes by explicitly converting a primitive into an object:

var str = new String('Hello World'),
str.type = 'greeting';

console.log(str.type); // 'greeting'

Note

You don't have to worry about the performance implications of this wrapping process. Even if wrapper objects are created and destroyed for each property access, all modern JavaScript implementations handle the process efficiently.

Because of this automated wrapping process, primitive types can be used just like normal objects. The existence of constructor functions for primitive types also makes it possible to add new properties and methods that will be "inherited" by primitive values. Primitive types are represented in the MooTools type system by the type objects Boolean, Number, and String.

As a final note before we move on, be reminded that numeric literals need to be enclosed in a pair of parentheses when they are used in property-access operations, like (42).hasOwnProperty(). Forgetting to do so—like 42.hasOwnProperty()—results in a syntax error because the parser will think you're declaring a floating point literal.

Composite Types

JavaScript has only one composite type: the Object type. However, it can be divided into subtypes that define the special kinds of objects, such as arrays or dates. Unlike the native type system, the MooTools type system differentiates between objects according to these subtypes.

We already talked about objects in detail before, so we won't go into the internals of JavaScript's object implementation here. We know that objects are aggregate values composed of an unordered collection of key-value pairs. We also know that all objects are associated with a constructor function and they inherit from the prototype property of their respective constructor.

Unlike primitives, objects are mutable: their structure and their behavior can be changed. When stored in variables or passed as arguments, new copies of the object aren't created. Instead, a reference to the original object is passed, effectively "sharing" the same object in all operations. Because of this behavior, objects are also called reference types.

The ECMAScript specification divides objects into two kinds:

  • Native objects (also called built-in objects) are objects provided by the language and defined directly by the ECMAScript specification. All complying ECMAScript implementations provide the same set of native objects. Built-in constructor functions and their prototypes, the Math object, and native functions like eval are all examples of native objects.

  • Host objects are objects provided by the host environment (i.e., the interpreter) for the purpose of correctly executing an ECMAScript program. They are independently defined by a particular JavaScript implementation and are therefore "non-standard" when viewed against the ECMAScript specification. The DOM objects of a browser, the Module functions of a CommonJS engine, and the additional objects created by a JavaScript implementation are examples of host objects. By virtue of their being a language extension rather than a simple framework or library, we'll consider the objects provided by MooTools as host objects.

Note

As an aside, the language we know as "JavaScript" is technically the ECMAScript language plus a standard set of browser-specific host objects. Thus, all JavaScript implementations are ECMAScript implementations—but not all ECMAScript implementations are JavaScript implementations.

We have already seen some of these native and host objects, like functions, objects, and classes, in the previous chapters. We'll discuss most of the browser-specific host objects in the second part of this book, and the CommonJS host objects in part three.

Object-to-Primitive Casting

Casting objects to primitive types follows different rules depending on the kind of object being converted.

The easiest rule is object-to-boolean value casting: all objects are cast to the boolean value true. You should take note of this since even a boolean object created using new Boolean(false) is cast to true. Its internal value might be false, but it's still cast to true since all objects are truthy.

When casting objects to strings, the toString method of the object is called. All objects inherit a basic toString method from Object.prototype, which returns a string in the form '[object <Class>]'. <Class> in this form refers to the internal class property of the object, so a basic object will return '[object Object]' while an array will return '[object Array]'.

However, most native objects override the toString method with their own implementation that usually returns a more appropriate string value. The primitive wrapper objects, for instance, have toString methods that return the string representations of their primitive values as described above.

To cast objects to numbers, JavaScript uses another object method called valueOf. If the return value of this method is a number, or if it can be cast into any number that is not NaN, then this value is used as the numeric value of the object. Otherwise, the toString method of the object is called and the return value of that method is again cast into a number. If the return value of toString is still not a number value, the object is cast to NaN.

The default valueOf method inherited from Object.prototype returns the object itself, which means that all objects are cast to NaN by default. Primitive wrapper objects, however, return their primitive values with the valueOf method, and return their string-cast values for toString. The primitive values inside object wrappers are then cast according to the rules we saw in the previous section.

Not all built-in objects provide their own valueOf method, so it's safe to assume that most objects will be cast to NaN. We'll take note of how a particular native object type implements the valueOf and the toString methods as we discuss them.

The Base Object

At the top of the object hierarchy is the basic object, created using an object literal or via the Object constructor. The Object constructor represents the base object, and all objects inherit from Object.prototype.

The Object constructor, unlike other built-in constructors, is not turned into a type object by MooTools in order to prevent extension of its prototype. This is because Object.prototype is considered off-limits: no new properties or methods should be added to it. To understand why, let's examine a basic JavaScript program:

// a basic object
var obj = new Object();

console.log(typeOf(obj.constructor));    // 'function'
console.log(typeOf(obj.hasOwnProperty)); // 'function'

for (var key in obj) console.log(key);

Using the typeOf function, we confirm that a base object inherits properties and methods like constructor and hasOwnProperty from Object.prototype. We then use the for-in statement, which loops through all the members of an object, to print out the names of our objects members. If we execute this program using a JavaScript interpreter, though, the console.log(key) in the for-in statement will never be executed—and it will appear as though our object has no members.

The reason for this behavior is that JavaScript differentiates between enumerable and non-enumerable properties. An enumerable property is any property that will be listed by a for-in loop, while a non-enumerable property is the opposite. All non-enumerable properties are thus considered to be "invisible" in a for-in loop.

All default properties and methods of Object.prototype are non-enumerable to ensure that new objects appear "blank." This is a way to avoid confusion among new developers: unless you know the internals of the prototypal system, you might be surprised to see new objects already having properties and methods. Therefore, the properties and methods that are defined by the language itself are marked as non-enumerable by default to prevent novices from thinking that these methods were added directly to the object.

On the other hand, all properties and method that we define in code are enumerable by default. If we augment a native object or replace its existing member with our own value, the new value we add will appear in for-in statements. In the case of Object.prototype, this makes things go awry because all objects inherit from this prototype. New objects no longer appear "blank" and you no longer have a way of knowing whether a member was implemented natively or if it was added directly to the object without having to call the hasOwnProperty method.

Because it is currently impossible to implement non-enumerable properties in most JavaScript interpreters, the JavaScript community has decided that Object.prototype should never be augmented. As one JavaScript saying goes: "Object.prototype is verboten." The MooTools developers agree with this statement, and it was decided that the Object constructor itself should not be turned into a type object to prevent misuse.

Note

I stated that it's currently impossible to implement non-enumerable properties in most interpreters because, at the time of writing, most of these implementations still adhere to ECMAScript 3. ECMAScript 5, on the other hand, allows setting properties that are non-enumerables through descriptors.

Functions

Functions represent distinct, standalone chunks of executable code. They are the only native object type that has the distinction of being given a different "type" by the typeof operator. This is because functions are the only objects that have associated executable code that is run when the function is invoked.

A function object's executable code is stored in a special internal property called call (not to be confused with the call method). When either the invocation operator, (), or the apply and call methods of a function object are invoked, the executable code of the internal call property is executed by the interpreter. This internal call property is also the distinguishing property by which the typeof operator differentiates a function from any other object: the ECMAScript specification explicitly defines that only functions have this internal call property.

We've already discussed the internal details of functions in Chapter 2, so we won't go too deep here. The Function constructor, which can be used to create function objects, is the main constructor for functions, and all functions inherit from Function.prototype. The MooTools type system automatically turns Function into a type object.

Function.prototype doesn't implement its own valueOf method, which means calling fn.valueOf() will return the function itself. The default toString method, on the other hand, is overridden, and it returns the source code of the function. Take note, however, that the output of the toString method is implementation-dependent: not all JavaScript engines will output the same string source.

One other distinctive feature of functions is that they are the only other object type—along with regular expressions—that can't be implemented using a basic object. You can implement a JavaScript version of any other native object type using a basic object literal, but you can't make a function by simply using an object literal. Because of this, functions can't be subclassed using normal inheritance patterns, and this presents a very unique problem for both classes and type objects that we'll see later on.

Arrays

An array is an object representing an arbitrary-length, ordered collection of values. JavaScript arrays can store any valid value, including objects, functions, and other arrays. And unlike its counterparts in some programming languages, a JavaScript array doesn't expect all values to be of the same type, which means you can store values with different types in one array. All arrays inherit from the prototype property of the Array constructor.

Each value in an array is called a member and each member is associated with a numeric index that signifies its position in the collection. Arrays have a special dynamic property called length, which represents the number of members in an array. Arrays are usually created using the array literal, which is a pair of square brackets enclosing a set of values separated by commas, like [1, 2, 3].

Arrays inherit the default valueOf method from Object.prototype, and calling this method simply returns the array itself. Meanwhile, the toString method is overridden in Array.prototype: it returns the string representation of each member of the array separated by a comma, like '1,2,3'. Notably, this string value is the same as the result of calling the join method of arrays:

[1, 2, 3].join(','), // '1,2,3'

JavaScript arrays are implemented differently from the "real" arrays of other programming languages. A JavaScript array is nothing more than an object with numeric indices as keys and with a dynamic property length—plus additional methods inherited from Array.prototype. In fact, the array above could have very well been written as:

{
    '0': 1,
    '1': 2,
    '2': 3,
    length: 3
}

This is the basic structure of a JavaScript array. The numeric indices for this example—as well as for regular array—are strings, not numbers, since object keys need to be valid JavaScript identifiers. When numeric values are used to access the members of an array or an object using the bracket notation like in our example, the interpreter automatically converts these numbers to strings—something we can verify by using our two examples in a similar way:

// real array
var arr = [1, 2, 3];

for (var i = 0, l = arr.length; i < l; i++){
    console.log(arr[i]);
}

/* output:
    1
    2
    3
*/

// "object" array
var objArr = {
    '0': 1,
    '1': 2,
    '2': 3,
    length: 3
};

for (var i = 0, l = objArr.length; i < l; i++){
    console.log(objArr[i]);
}

/* output:
    1
    2
    3
*/

Both the real array and the dummy array created using a basic object behaved the same in this snippet because the implementation of JavaScript arrays is based on regular objects with a few minor differences. First, real arrays inherit from Array.prototype, which enables us to use nice array methods like forEach, and second, the length properties of real arrays are dynamically updated. The length property also behaves differently from other properties because changing its value by assignment actually changes the number of members in an array.

Our dummy array actually represents another set of objects in JavaScript called array-like objects. As its name implies, an array-like object is an object that looks similar to an array: it has numeric indices for keys and it has a length property that reflects the number of members it contains. The arguments object of functions, DOM collections, and nodelists are examples of array-like objects.

Because these objects aren't true arrays, they don't inherit from Array.prototype and you can't call array methods through them. However, you can invoke the methods of Array.prototype and bind the this keyword to these array-like objects to perform array operations using them. For example, we could turn our dummy array-like object into a true array using the Array.prototype.slice technique we used in the chapter on functions:

var objArr = {
    '0': 1,
    '1': 2,
    '2': 3,
    length: 3
};

console.log(typeOf(objArr)); // 'object'

// turn it into a true array
objArr = Array.prototype.slice.call(objArr);

console.log(typeOf(objArr)); // 'array'

All array methods can be used for array-like objects with this technique, not just slice. This is possible because the methods of Array.prototype are implemented as generics, which means they can be used on any object.

Arrays, for example, have the methods unshift and push, which add members to the beginning and the end of the array, respectively. We can use these two methods to augment our array-like object using the same approach as with slice:

var objArr = {
    '0': 1,
    '1': 2,
    '2': 3,
    length: 3
};

console.log(objArr.length); // 3

// the first member of the array
console.log(objArr[0]); // 1

// add a member to the front
Array.prototype.unshift.call(objArr, 0);

console.log(objArr[0]); // 0
console.log(objArr.length); // 4

// add a member to the back
Array.prototype.push.call(objArr, 4);

console.log(objArr[4]); // 4
console.log(objArr.length); // 5

// check that it's still an object
console.log(typeOf(objArr)); // 'object'

The array methods not only modified our object as if it were a real array, they also automatically adjusted the length property of the object. This proves that JavaScript arrays are truly implemented using simple objects, and it also gives us a very useful technique we can use for building our own array-like objects.

Regular Expressions

Regular expression objects or regexps are used for the string pattern-matching feature of JavaScript. Regular expressions are created using the RegExp constructor or using a regular expression literal, and they inherit from RegExp.prototype. Like arrays, JavaScript doesn't consider regexp objects as separate object types, but rather as special versions of the basic object type.

The toString method of RegExp.prototype will return the string version of a regular expression literal. This string representation is implementation-specific, and is sometimes parsable by the RegExp constructor. Meanwhile, the valueOf method of regexps is inherited from Object.prototype, so it returns the object itself.

Note

In ECMAScript 5, the toString() method of a regexp object is required to return a string that's parsable by the RegExp constructor.

Curiously, some JavaScript implementations will output 'function' when the typeof operator is applied to a regexp object. While it is true that both functions and regular expressions share the same trait of not being implementable using a basic object, this behavior is actually a byproduct of a buggy feature addition that was wrongly copied by other implementations.

Mozilla implemented a special feature for its SpiderMonkey JavaScript engine that allowed direct calls to regexp objects as though they were functions. For example, this feature allowed the expression /string/.exec('string') to be shortened to /string/('string'). Mozilla added the ability to use the invocation operator on regular expressions—just like functions—and doing so would automatically call the exec method of the regexp object.

To implement this feature, Mozilla added an internal call property to the regexp object—a property that should be available only to functions, according to ECMAScript standards. This internal call property of the regexp object exploits the same technique of binding executable code to an object as in the case of functions, but here the executable code is actually the regexp's exec method. And this is where everything went haywire.

The typeof operator only checks a single property to determine whether an object is a function: the internal call property. However, because of this new feature added by Mozilla, the typeof operator gets tricked when it encounters regular expression objects, and it mistakenly outputs 'function' rather than 'object'.

This might not have been such a big deal if not for the fact that this feature—along with its bug—was copied by other implementations. JavaScriptCore, the JavaScript engine of the Webkit project and the Safari browser, implemented this same feature. Google, in developing its Chrome browser, also used Webkit, but replaced JavaScriptCore with its own JavaScript implementation called v8. Unfortunately, this new engine was implemented to remain compatible with the JavaScriptCore engine it replaced, so the same feature and the same bug persisted.

Because of this bug, it's impossible to actually differentiate between real functions and regexp objects through the typeof operator alone in some browsers. Mozilla has fixed this bug in the version of SpiderMonkey that shipped with Firefox 3, but it still persists in the current versions (at the time of writing) of Chrome and Safari. Fortunately for us, the MooTools typeOf function is immune from this bug.

Regexps are a powerful feature of any programming languages, and they allow us to retrieve parts of a string using patterns. We won't be discussing them in detail in this book, but we'll see much of their use in the chapters that follow.

Dates

Date objects represent calendrical values. They are created using the Date constructor and they inherit from Date.prototype. Unlike other built-in types, dates don't have a corresponding literal form: all date objects need to be created using the Date constructor function.

Essentially, a date object is a collection of numeric values that represent a specific date value. Stored inside a date object are values for the year, month, day, hour, minute, second, and millisecond that represent a specific calendrical value. However, date objects are fully encapsulated: these values are accessible only through getter and setter methods defined by Date.prototype.

The toString method of a date object returns a string representation of the date value in the form <weekday> <month> <day> <year> <hours>:<minutes>:<seconds> <timezone>, like 'Wed Jun 03 1987 19:30:00 GMT+0800 (PHT)'. This string representation is parsable by the Date constructor, so you can use it to build a similar date object.

Note

As with the regexp object, the return value of the date toString method is implementation-specific. However, all major JavaScript engines follow the format presented above.

Similarly, the valueOf method from Date.prototype also returns the value of the date object, but as a numeric timestamp. This timestamp is a representation of the number of milliseconds since January 1, 1970 00:00:00 GMT (often called the Epoch). Calling the valueOf method for the date object above therefore yields 549718200000. This numeric value is also the same value returned by the getTime date method.

Note

The epoch timestamp in most programming languages is a 10-digit number, plus additional decimal places to denote offsets. JavaScript, in contrast, uses a 12-digit timestamp.

Date objects are the only native objects that can be cast to their proper strings and number values.

Error Objects

ECMAScript designates a group of objects called error objects that are used for error-handling operations. When the JavaScript interpreter encounters problematic code, it "throws" an error object that can then be handled by the program through the try-catch statement.

The main error constructor, Error, represents the most basic type of error object. It has a name property, the default value of which is "Error", and a message property, which contains the specific human-readable error message. Some JavaScript implementations also add other non-standard properties, like the line number of the error or the stack trace for the execution context where the error occurred.

JavaScript itself doesn't use the base Error constructor, though. Instead, it uses special error subclasses:

  • EvalError—for errors involving the eval function.

  • RangeError—for errors when numeric values exceed the defined bounds of possible numeric values.

  • ReferenceError—for errors that happen when performing operations on invalid references, such as accessing the properties of an undefined variable.

  • SyntaxError—for errors that arise from being unable to parse improperly written code.

  • TypeError—for errors involving passing incorrect value types to operations.

  • URIError—for errors involving the URI encoding and decoding functions.

The name properties of these error subclasses are the same as the identifiers of their constructor functions.

The valueOf method of error objects is inherited from Object.prototype. Thus, they return the objects themselves. Meanwhile, the toString method of error objects is implementation-specific, and weirdly enough, the return value of this method isn't required by the specification to be the actual error message—which means that implementations can simply output anything they want. Thankfully, most JavaScript implementations do return the value of the message property of the error object when the toString method is invoked.

Note

In ECMAScript 5, the toString method of error objects is required to return strings in the form '<ErrorType>: <message>'.

The MooTools framework has a unique policy of handling errors gracefully and silently, so errors are rarely used. Thus, Error and the other built-in error subtypes are not turned into type objects.

Type Casting

We've seen the rules on how values are cast from one type to the other, but we didn't actually find out how to convert objects from one type to another. Type casting can be tricky in some cases, so we need to discuss how values of one type can actually be converted to another type.

Like other dynamic languages, JavaScript actually performs automatic, or implicit, type casting. Implicit type casting is when values are cast from one type to another by the interpreter in order to properly execute an operation. These transformations are usually silent and aren't noticeable unless they're carefully observed.

The best example of implicit type casting is the automatic wrapping of primitives with object wrappers when the primitive values are used in operations that require objects. The interpreter silently wraps the primitive value with an object wrapper, and we don't really need to do anything to make this happen.

Similarly, the values null and undefined, as well as all object values, are turned into primitives when they are used in operations that require primitives. For instance, using an object in a division or multiplication operation automatically casts the object into a number value. Similarly, statements that require boolean values, like the if statement, as well as the property access operator [], also do implicit boolean and string casting. And wrapper objects for primitives are automatically cast to their primitive values if needed.

Note

JavaScript has numerous implicit casting rules, and we can't cover all of them in this section. Instead, I advise you to read one of the recommended books on JavaScript noted in the Resources section at the end of this book.

In contrast to implicit casting, explicit type casting is the process of directly transforming one value into another using special operations. There are several ways to explicitly cast a value from one type to another, and we'll explore each one in turn.

Casting Using Constructors

The first and easiest way to explicitly cast a value to another type is to use the built-in constructors. Most native JavaScript constructors can actually be invoked as regular functions, and they perform type conversions when used this way.

The constructor objects for primitive wrappers, for instance, can be used to convert values to primitive types:

// boolean
console.log(Boolean(0));        // false
console.log(Boolean(''));       // false
console.log(Boolean('24'));     // true
console.log(Boolean({}));       // true

// number
console.log(Number(false));     // 0
console.log(Number(''));        // 0
console.log(Number('24'));      // 24
console.log(Number({}));        // NaN

// string
console.log(String(false));     // 'false'
console.log(String(24));        // '24'
console.log(String({}));        // '[object Object]'
console.log(String([1, 2, 3])); // '1,2,3'

The casting operations here follow the same rules we described earlier: primitives are converted using the rules for converting one primitive type to another, while objects are converted using their valueOf and toString methods.

An important thing to remember is that the return values of these primitive conversions are actual primitive values and not objects—only when used in conjunction with the new operator do they return objects. Thus, String(1) will return the primitive string value '1', while new String(1) will return a new string object.

Native object constructors, on the other hand, behave differently when used as regular functions. The Function, Array, RegExp, and Error constructors, when invoked as regular functions, operate the same as if they were used with the new keyword. Thus, they don't actually perform type casting, but rather, they perform object instantiation. It is therefore advisable not to invoke them as regular functions.

The Date constructor's behavior, in contrast, is unique when it's invoked as a function: it doesn't perform type casting nor does it create a new date object. Instead, it returns the current date as a string.

Finally, the Object constructor performs type casting according to the actual type of value passed. If you pass in a primitive value, it will return a wrapped object version of that value—so you'll receive either an instance of String, Number, or Boolean. But if you pass in an object value, the constructor will simply return the same object, with no modification. And if you pass in null or undefined, it will return a new plain object—just like doing new Object().

Casting Using Native Functions and Idioms

There are two special functions defined in JavaScript that handle string-to-number conversions: parseFloat and parseInt. Unlike the Number constructor, these functions are more lenient when used for parsing strings, as they allow non-numeric trailing characters in the strings.

The parseFloat function converts both integer and floating point numbers, while parseInt can only convert to integers:

console.log(Number('42 is the answer.'));       // NaN
console.log(parseInt('42 is the answer.'));     // 42
console.log(parseFloat('42 is the answer.'));   // 42

console.log(parseInt('3.14'));      // 3
console.log(parseFloat('3.14'));    // 3.14

console.log(parseInt('024'));       // 20
console.log(parseInt('024', 10));   // 24

The last two lines feature a quirk with the parseInt implementation. The parseInt function has a special "feature" that treats strings that begin with 0 as octal values. Thus, parseInt('024') returns the value 20 instead of 24. This creates a problem when parsing non-octal strings that start with 0, although you can solve it by passing a second argument, radix, which tells the function what base to use for the conversion. This "feature" has been removed in ECMAScript 5.

One idiom that's used to convert any value to a number is JavaScript's implicit type conversion for mathematical operations. By using numeric identity operations (such as subtracting 0 from a value or dividing or multiplying a value by 1), you can convert a value to a number:

console.log('42' / 1); // 42
console.log('42' - 0); // 42
console.log('42' * 1); // 42

console.log(true - 0); // 1
console.log(false * 1); // 0

console.log({} - 0); // NaN

But don't use the addition identity operation (x + 0) because the + operator is both the addition and concatenation operator in JavaScript. You can, however, use + as a unary operator for the purpose of numeric casting:

console.log(+'42'), // 42
console.log(+true); // 1
console.log(+{}); // NaN

To convert any value (except for null and undefined) to a string, we can simply use the toString method. This works with primitives as well because of the automatic object wrapping mechanisms:

console.log(true.toString());       // 'true'
console.log((42).toString());       // '42'
console.log([1,2,3].toString());    // '1,2,3'

Note

Number.prototype.toString() is a special case, because it allows you to specify an argument, radix, that will be used as the base for conversion. By default, the radix value is 10.

Another way to convert values to strings is to exploit the concatenation operator, +. This operator is used for both addition of numbers and concatenation of strings, but it gives special priority to string values: if one of the operands in the expression is a string, it also casts the other value into a string. We can therefore turn any value into a string by concatenating it with an empty string:

console.log(true + ''),     // 'true'
console.log(42 + ''),       // '42'
console.log([1,2,3] + ''),  // '1,2,3'

Finally, casting values to booleans is rarely done since JavaScript allows any value to be used in place of an actual boolean value when needed. If you do want to convert a particular value into its boolean representation quickly, though, you can use the double-negation trick:

console.log(!!''), // false
console.log(!!'M'), // true

console.log(!!0); // false
console.log(!!42); // true

console.log(!!{}); // true
console.log(!![]); // true

The negation operator, !, automatically casts a value into a boolean and it reverses a boolean true value to false and vice versa. Using the negation operator twice yields the same boolean value that was negated at the start of the expression. For example, the number value 0 is cast to the boolean value false. When negating this value, we get !false == true. The true value is then negated again, and the final value goes back to false. Thus, we get a proper conversion from 0 to false.

The MooTools Type System

Now that we've familiarized ourselves with the native type system and its components, let's turn our attention to its MooTools counterpart. The most important component of the MooTools type system is the Type constructor, and it is this simple constructor function that enables us to streamline the process of working with native types.

The Type Constructor and Function Subclassing

The Type constructor accepts two arguments: a required name argument, which should be a string representation of the capitalized type name, and an optional object argument, which is the constructor function to be transformed. If an object argument is passed, the Type constructor returns the same object after it adds additional properties and methods to it. Otherwise, it returns null.

As I mentioned earlier, the Type constructor doesn't create new constructor functions but rather transforms already existing constructors into type objects by augmenting new properties and methods to it. To illustrate, here's how the native Array constructor is turned into a type object:

new Type('Array', Array);

And that's all it takes to turn a native constructor into a type object. We simply instantiated a Type object using the new keyword and passed the name of the type, 'Array', and the native Array constructor function. You'll notice that we didn't even need to store the results in a variable. Because the Array constructor is transformed directly, there was no need to store the result of the expression in a new identifier. The process is both simple and elegant—and certainly says a lot about the MooTools type system implementation

One question that continually pops up when developers see that example is whether the new keyword is actually needed. If we're not really creating a new object but simply transforming an existing constructor function into a type object, why not just make Type a simple function? Does it really have to be a constructor?

The question is even more valid once you consider the fact that instances of Type are not really "instances" of Type:

new Type('Array', Array);

// make sure that Array is a type object
console.log(typeOf(Array)); // 'type'

// is Array an instance of Type?
console.log(Array instanceof Type); // false

We do know that Type transforms the constructor directly, so it really doesn't create a new object. The type object returned by the Type constructor isn't really an instance of Type since it's a function that already existed before we passed it to the constructor. So does this mean we could really do away with using new?

To answer this question, we must first examine the same question but with regard to classes, which are similar to type objects. Unlike the Type constructor, the Class constructor actually creates a new constructor function for the class, so using the new operator with Class seems like a logical thing to do. What's surprising is that the same behavior can be observed in classes as well:

var Person = new Class();

// make sure that Person is a class
console.log(typeOf(Person)); // 'class'

// is Person an instance of Class?
console.log(Person instanceof Class); // false

This seems a little counter-intuitive: the Class constructor creates the new constructor for the class, so we'd expect that the result of instantiating the Class constructor would be a class instance. But when the instanceof operator is used to check, classes exhibit the same behavior of not being instances of the Class constructor—just like type objects with Type.

Recall something I mentioned earlier in this chapter about functions: they are one of the two types of objects in JavaScript that can't be created using a basic object (the other being regular expressions)—which means they can't be subclassed. The reason for this is simple: functions depend on an internal call property that references the executable code that's called when the function is used in conjunction with the invocation operator. Since we have no way of setting this internal property in regular objects, we can't create subclasses of the Function type because we'll end up referencing the same executable code.

The Type and Class constructors both deal with constructor-prototype pairs, with emphasis on constructors. In essence, type objects and classes are "subclassed" functions: they're constructor functions that have special properties and methods. But because JavaScript places no distinction between regular functions and constructors (except for their behavior when used with new), both Type and Class need to circumvent the limitation of function subclassing using direct augmentation of functions, rather than simple prototypal inheritance.

Here's how it works. For the type definition new Type('Array', Array), a new object inheriting from Type.prototype is created by the new operator and then used as the this value inside the Type constructor. Type then takes all the properties and methods of this new instance and adds them to the Array constructor function, thereby making sure that the resulting Array type object will "inherit" the members in Type.prototype. The operation involves direct augmentation of members to the type object, and not inheritance via the prototype chain. The new keyword is essential because it creates the template object that will be used for augmentation. The same thing is applicable to the Class constructor, with the minor difference of the resulting class object being created inside the Class constructor itself.

Of course, direct augmentation has its limitations, which we already saw in the Chapter 3. The main issue would be dynamic modification: since type objects and classes are augmented directly, any changes to Type.prototype and Class.prototype aren't propagated to existing type objects and classes. This isn't a big issue in practice, though, since the number of type objects and predefined classes in the MooTools-Core library are small enough to modify directly.

The snippets above don't just show why we need something like the new operator when we're creating new type objects, they also shows us some limitations encountered when trying to implement a new type system on top of the existing one. In order for the replacement MooTools type system to be fully usable, it needs to cover not only the creation and management of types but also things like instance checking and type detection.

Interestingly, the core mechanisms for adding these two features to the MooTools type system are implemented by the Type constructor itself, as we'll see in the next two sections.

Instance Checking

Native JavaScript instance checking is done using the instanceof operator. We've already seen it in action several times in this and earlier chapters, so it hardly needs any introduction.

The instanceof operator works by comparing the constructor of the object on the left-hand side of the expression to the constructor function on the right-hand side. In the expression myCar instanceof Car, for example, the constructor function that created the myCar object is compared to the Car constructor to see if they're the same function. If they are, the expression evaluates to true.

However, if the constructor function for the myCar object and the Car constructor aren't the same, the expression doesn't immediately evaluate to false. Instead, the prototype chain of the myCar object is traversed, and the constructor function of each object in the prototype chain is compared with the Car constructor. Only when the last constructor function has failed comparison will the expression evaluate to false.

This "deep" comparison is why the instanceof operator evaluates to true not only for the immediate constructor of the object, but also the ancestral constructors of the object. All objects are therefore seen as instances of the Object constructor, since all objects inherit from Object at one point in their prototype chain.

As you've probably guessed by now, the constructor property of objects plays a central part in native instance checking. All objects are linked to their corresponding constructor functions via their constructor property, and it's the value of this property that gets compared to the constructor function on the right-hand side of the expression.

In our examples in the previous section, both type objects and classes fail the instanceof test because they don't have links to the Type and Class constructors directly. Both type objects and classes are abstracted constructor functions, so their prototype chain consists only of instances of Object and Function. Because the Type and Class constructors augment these constructor functions directly rather than through prototypal inheritance, their constructor functions never get "linked" to the object. Thus, type objects and classes are not seen as true instances of their respective constructors by the instanceof operator.

To circumvent this limitation, a link has to be made by the MooTools type system between these objects and their constructors. This is done by adding a new property to these objects called $constructor. Like its native constructor counterpart, the $constructor property is a reference to the object's original constructor function. Type objects therefore have their $constructor properties pointing to Type, while classes have theirs pointing to Class.

Of course, just because MooTools added a new property that resembles the native constructor property doesn't mean that instanceof will respect it. The MooTools type system works on a separate level from the native one, so we can't expect a simple solution like that to take care of the issue altogether. Instead, MooTools adds the second part of the equation by creating a new function to replace the instanceof operator: the instanceOf function.

The instanceOf function takes in two arguments: item, which is the object being checked, and object, which should be a constructor function to check against. Unlike the instanceof operator it replaces, though, the instanceOf function properly handles cases like type objects and classes:

// Type Object
new Type('Array', Array);

// make sure that Array is a type object
console.log(typeOf(Array)); // 'type'

// is Array a native instance of Type?
console.log(Array instanceof Type); // false

// is Array an instance of Type in MooTools?
console.log( instanceOf(Array, Type) ); // true


// Class
var Personn = new Class();

// make sure that Person is a class
console.log(typeOf(Person)); // 'class'

// is Person a native instance of Class?
console.log(Person instanceof Class); // false

// is Person an instance of Class in MooTools?
console.log( instanceOf(Person, Class) ); // true

Revamping our examples from the previous section, you'll notice that instanceOf(Array, Type) and instanceOf(Person, Class) now both return true. This tells us that Array and Person are instances of Type and Class respectively—which is exactly what we would want to know.

Note

Like typeOf, the instanceOf function isn't an operator like its native instanceof counterpart but a real function. Therefore, the use of the invocation operator is essential, as well as proper casing (it's instanceOf(), not instanceof()).

Like its native counterpart, the instanceOf function works by comparing the constructor of the item argument with the constructor function that's the value of the object argument. Unlike the native instanceof operator, though, instanceOf is aware of the $constructor property and uses this property instead of the regular constructor property as much as possible. The comparison process involves a direct equality test, which means that instanceOf(Array, Type) is somewhat equivalent to Array.$constructor == Type.

I said "somewhat equivalent" because the comparison isn't a one-off process. Like the native instanceof operator, the instanceOf function also does "deep" comparison, so checking whether an object is an instance of some ancestral constructor also works:

new Type('Array', Array);

console.log(instanceOf(Array, Type)); // true
console.log(instanceOf(Array, Function)); // true
console.log(instanceOf(Array, Object)); // true

The creation of the $constructor property is done by the Type constructor (and by the Class constructor for classes). When the constructor function argument is processed by Type, it adds a $constructor property to the constructor function that's used for the instanceOf function. The $constructor property of the constructor function passed to Type is always set to the Type function itself, so that instanceOf will be able to determine that the type object is an instance of the Type constructor.

However, the Type constructor doesn't just add a $constructor property to the constructor argument, but also to the prototype of the constructor argument. In the snippet above, for instance, Type doesn't just set Array.$constructor to Type, but also adds the property Array.prototype.$constructor. The $constructor property of the prototype is then set to the constructor itself, so in our example, Array.prototype.$constructor == Array. This additional prototype property creates one big difference between the instanceof operator and the instanceOf function: the ability to check primitive values.

The instanceof operator, by design, only works if the left-hand side of the expression is an object, not a primitive. If you use a primitive as the value of the left-hand side, it won't work: 'hello' instanceof String evaluates to false. The instanceof operator does not perform type-casting, so primitive values are treated as true primitives and not objects.

However, the instanceOf function doesn't have the same limitation. Because it depends on the $constructor property rather than the true native type of the value, the instanceOf function is able to determine types even for primitive values. In the process of accessing the $constructor property of the value passed, primitives are automatically turned into objects within the instanceOf function, and therefore inherit the $constructor property from their respective type objects. Thus, instanceOf('hello', String) evaluates to true.

Before we move on, though, it's important to note that the instanceOf function also uses instanceof internally as a fallback mechanism in case regular $constructor checking doesn't work. This is an important thing to remember because of a special group of types that don't have type objects, which we'll encounter in a bit.

Type Detection

JavaScript gives us the typeof operator for detecting the types of our values. But as we saw earlier in this chapter, the native typeof operator leaves a lot to be desired. Its replacement from the MooTools type system, the typeOf function, does a better job at detecting the types of values as well as differentiating among the actual types of objects.

Unlike the instanceOf function, though, which only depends on the constructor and $constructor properties of the value passed, the typeOf operator uses a couple of different ways to properly detect the type of a value. At the top of the list is the use of type objects to pass special functions to their instances, which are called family methods.

A family method is a special private method that's added by the Type constructor to the prototype of the type object that's used to return the type of the object. It is a very simple function that simply returns the lowercase name of the type as a string. When creating new Type('Array', Array), for example, the Type constructor adds a new method called $family to Array.prototype, and this new method simply returns the lowercase equivalent of the name passed to the Type constructor. Thus, Array.prototype.$family returns 'array' when invoked.

This family method is then invoked by the typeOf function to return the type of the value. Because the family method is added to the prototype of the type object directly, all instances of the type will therefore inherit the same family method. So detecting the type of an object is as simple as calling its $family method. In the case of arrays, for instance, doing [].$family() or typeOf([]) returns 'array' for both cases—which is the actual type of the object. The typeOf operator simplifies this process of calling the $family function of the object by doing it automatically.

You might be asking then, "What's the use of calling typeOf then, when I could just invoke the $family method directly?" Well, the first answer is because typeOf is part of the private API—and like many things in the private MooTools API, it's bound to change eventually. Making direct calls to this method makes your code prone to breakage when new versions of the library come out—especially ones that change the internal API dramatically (and it happens!).

But even if we're absolutely sure that it will never break—and that's highly unlikely—there's an even bigger reason why you should not call the $family method directly:

// regular object
var obj = {};

// typeOf
console.log(typeOf(obj)); // 'object'

// obj.$family
console.log(obj.$family()); // this will throw an error

In this snippet, passing the obj variable to typeOf works as expected, and we get the proper type—'object'—as the return value. However, if we try to do obj.$family(), we'll get an error—because obj has no method named $family.

Remember that the $family method is inherited by objects from their type objects, and it is added directly to the prototype of the type object via the Type constructor. However, some objects don't have a $family method because they don't inherit the method from their type objects. In fact, it's just not the case of not inheriting the method; it's a case of not having actual type objects in the first place!

Some native constructors, Object being the most notable, are not turned into type objects by the MooTools type system. A few of them aren't turned into type objects because they're not used in common programming tasks, while some aren't converted to impose restrictions. The Object constructor is the chief example of that second reason: it isn't turned into a type object to prevent extension of Object.prototype.

One other important set of types are those with no real constructors. For instance, argument objects, which are created for use inside functions, don't have a corresponding constructor—there's no Argument constructor in JavaScript. Other notable examples are DOM objects like textnodes and whitespaces, which also don't have constructor functions.

The MooTools type system recognizes these types even though they don't have corresponding type objects. However, it presents a little challenge to using $family directly. Because not all values inherit a $family method, we can't depend on this function alone to detect the type of a value. So, as in the case of our example above, we can't call the $family method directly because there's a risk that the object might not have the method to begin with.

This is the prime reason why the $family method should not be used as a replacement for typeOf. The typeOf function is smart enough to know whether the value in question has a $family method that can be called and does so if that's the case. If there's no $family method present, though, it uses another technique to detect the type.

So what's this other technique? It's called duck typing, and its name comes from the common saying: "If it walks like a duck and talks like a duck, then it must be a duck."

Duck typing bases its assumption of an object's type according to the structure of the object. As long as the object's properties and methods match the declared properties and methods for a type (walks like a duck and talks like a duck), it's considered to be of that type (it must be a duck). Duck typing is commonly used in dynamically typed languages to support substitutability. Because of the loose requirements of duck typing, you can design your code in such a way that it accepts objects of any type as long as they have a particular method or property.

However, very loose duck typing can't be applied to a type system directly, since we'd risk having false positives. For example, you can check for the existence of a length property to find out whether an object is an array, and it'll work for the most part, but it'll also think that string objects, argument objects, and element collections are arrays, since all of those appear to be array-like.

For duck typing to be really useful, the criteria for considering an object to be a type need to be very strict. The typeOf function uses several strict criteria to determine the type for values that have no type objects. Argument objects, for example, are tested not only for the presence of the length property, but for the existence of a callee property as well.

This, of course, isn't perfect and is easily bypassed by cunning manipulation of code. The code typeOf({length: 1, callee: 1}), for example, will return "arguments" even if what you have is not really an argument object. Unfortunately, there's nothing we can do about this, since duck typing is the only other solution we can use for values with no corresponding type objects. Thankfully, though, we can use the idea of duck typing to craft code that doesn't rely entirely on the specificity of type to do its job.

Take note that family methods take precedence in the typing process for typeOf. Duck typing is a fallback technique used only for objects with no corresponding type objects. And if these two techniques don't work, the typeof operator is used by the typeOf function as a last resort.

Working with Type Objects

Up to this point we've been talking about type objects in the context of type detection. But as I said earlier in this chapter, a type object also acts as the representative for its instances. Like classes, type objects are abstracted constructor-prototype pairs that can be used to change the structure and behavior of the items of that type. In fact, both classes and type object share very similar methods to accomplish this, as we'll see later on. And just like classes, type objects also have a special set of methods available only to them that can be used to streamline the process of working with types.

Implementing New Members

When working with native JavaScript, adding new properties or methods to existing types is done by augmenting the prototype object of their respective constructor. If we want to add a new repeat method for strings, for instance, we have to do something like this:

var str = 'hello';

console.log(typeOf(str.repeat)); // 'null'

String.prototype.repeat = function(times){
        var arr = [];
        while (times--) arr.push(this);
        return arr.join(''),
};

console.log(typeOf(str.repeat)); // 'function'
console.log(str.repeat(3)); // 'hellohellohello'

At the start of the code, the repeat method doesn't exist yet, so str.repeat is typed as 'null'. We then declare this new method by assigning a function to String.prototype.repeat. Afterwards, we check whether the repeat method is now available for strings, and we use it to repeat the string 'hello' three times.

We looked at this technique of augmenting the prototype property of a class in a previous chapter, and we saw how it's not the best way to add new members to a class. Adding new members directly to the prototype limits the additional features MooTools can offer, and the verbosity of the code could mean having to manage really complex declarations in the future. Instead of doing it that way, we learned that it's better to use the implement class method, which replaces the need for direct augmentation and streamlines the process of implementing new members.

Like classes, type objects also have an implement method, and we can use this method to rewrite the previous snippet:

var str = 'hello';

console.log(typeOf(str.repeat)); // 'null'

String.implement({

    repeat: function(times){
        var arr = [];
        while (times--) arr.push(this);
        return arr.join(''),
    }

});

console.log(typeOf(str.repeat)); // 'function'
console.log(str.repeat(3)); // 'hellohellohello'

The implement methods of classes and type objects are used similarly: implement accepts as an argument an object literal that describes the properties and methods you want to add and it loops through each of these members and adds them to the prototype.

While they appear to be the same function from the outside, the implement method of objects is not the exact same method as the one for classes. In the chapter on classes, we discovered the various internal features of the class implement method, including dereferencing, code wrapping, and mutator management. The type implement method also has some powerful internal features, although overall, it is much simpler than its class counterpart.

One of the more noticeable differences is that the type implement method does not do wrapping:

var myFn = function(){
    console.log('fn'),
};

// class
var Person = new Class();

Person.implement({
    method: myFn
});

var shiela = new Person();

console.log(shiela.method == myFn); // false

// type
String.implement({
    method: myFn
});

var str = 'hello world';

console.log(str.method == myFn); // true

In the first part of this snippet, we compare the method method of the Person class with the original myFn function using the line shiela.method == myFn. The result is false, which is expected because the implement method of classes wraps the original function in order to add additional class features. In the case of types, however, the methods added through implement are not wrapped but are added directly to the type's prototype object. Thus, when we do str.method == myFn, we get true—and this tells us that the methods are indeed the same function.

Newly added methods are not wrapped by the type implement method because the wrapper is no longer needed. Classes need method wrappers in order to implement protected methods and this.parent, but type objects don't support (or need) either of these features. Moreover, type objects are created using existing constructors that often already have existing members in their prototypes, and wrapping these existing methods might break them.

In the previous chapter, we learned about the protect method, which is used to implement protected methods in classes. The protect method has another use, in fact, and that is to protect a native method from being overridden. Consider the following example:

// original method
String.implement('method', function(){
        console.log('Original'),
}.protect());

'hello'.method(); // 'Original'

// override original
String.implement('method', function(){
        console.log('Override'),
});
'hello'.method(); // 'Original'

Here we first implemented a new String method called method which simply logs 'Original' in our console. However, we also called the protect method of the function value we're passing to implement, making our new method protected. When we tried to override this original method by calling implement again with a new method definition, our original function wasn't overridden, which is why we still get the same string in our logs.

The protect method is therefore very handy for making sure that native methods that browsers already implement aren't overridden by the user. By default, all the native methods for native objects defined by the ECMAScript specification are protected by MooTools, and therefore cannot be overridden by the user.

Aliases and Mirroring

Type objects support a special feature called aliasing, which is simply reimplementing an existing method using another name. It is done using the alias method, which takes two string arguments: the name for the new method and the name of the old method to be aliased.

var str = 'howdy';

// implement the original
String.implement({

    repeat: function(times){
        var arr = [];
        while (times--) arr.push(this);
        return arr.join(''),
    }

});

console.log(str.repeat(2)); // 'howdyhowdy'

// alias repeat
String.alias('again', 'repeat'),
console.log(str.again == str.repeat); // true

console.log(str.again(2)); // 'howdyhowdy'

In this example, we aliased the original repeat method of the String type simply by calling String.alias('again', 'repeat'). The alias method then takes the original function value of String.prototype.repeat and copies it to String.prototype.again. The result is that the two methods now point to the same function, making it possible to substitute calls to one for the other.

Aliasing is a very useful feature for creating shortcuts for commonly used methods with very long names. The Array method forEach, for example, is automatically aliased by MooTools into the each method, which is shorter and easier to type. You can do the same for other methods in any type objects to shorten your code and make it cleaner.

An important thing to note, though, is that aliasing uses implement internally, and all aliases methods are true references. Therefore, if you override the original method, your alias will not be updated:

var str = 'hi';

// implement original
String.implement({

    original: function(){
        console.log('hello'),
    }

});

String.alias('copy', 'original'),
console.log(str.copy == str.original); // true

str.original(); // 'hello'
str.copy(); // 'hello'

// override original
String.implement({

    original: function(){
        console.log('woot!'),
    }

});

console.log(str.copy == str.original); // false

str.original(); // 'woot!'
str.copy(); // 'hello'

In the first part of the snippet, both the original method and its aliased copy method point to the same function, and therefore act as the same method. However, when we reimplement the original method, the aliased copy method doesn't get updated.

Another useful feature that's related to aliasing is called mirroring. While aliasing allows you to reimplement a method on a type, mirroring allows you to copy any implemented method from one type to another. To perform mirroring, we use the mirror method of a type, which accepts a single argument that is a type object. Take a look at the following snippet:

var arr = [], str = 'hello';

console.log(arr.log); // undefined
console.log(str.log); // undefined

// make String "mirror" Array
Array.mirror(String);

// implement log on array
Array.implement({

    log: function(){
        console.log('log called.'),
    }
});

console.log(arr.log == str.log); // true

arr.log(); // method called.
str.log(); // method called.

The important line in this snippet is Array.mirror(String). This tells the type system that String is a "mirror" of Array, and any methods implemented on Array from then on should also be implemented on String. Because of this mirroring, the log method we implemented on the Array type is also implemented on String.

Mirroring makes it possible to automatically implement methods across multiple types. A single type can have multiple mirrors, which makes implementing the same method among many types easier and cleaner. The Array type, for instance, could have mirrors for other array-like objects, so that any method that's implemented for arrays would also be available to these objects.

It's the job of the implement method to manage mirrors. When the implement method of a type is called, it not only adds the properties and methods to the type object's prototype, but also checks whether the type has mirrors. The implement method then adds the properties and methods to these mirrors, making sure that the members are available to these types as well.

Because mirroring is managed by implement, only those members that are added after the mirror was declared would be automatically added. Existing properties and methods aren't copied, so we have to do that manually if we want to copy them. For instance, even though String was added as a mirror of Array, only the methods that were added via implement after the declaration would be copied—like the log method. Existing array methods, like splice or push, aren't copied automatically.

Another thing to consider is that mirrors aren't reciprocal: implemented methods on a type will be implemented on all its mirrors, but implemented methods for the mirrors aren't added for the type. In the example above, any method implemented on Array after the mirror declaration would be copied to String, but any method implemented on String won't be copied to Array. If you want reciprocal mirrors, you'll have to declare mirrors for both types.

The mirror method of type objects isn't actually limited just to type object arguments. We can also pass a callback function that will be invoked for every item that's implemented:

var callback = function(name, method){
    console.log(name);
};

Array.mirror(callback);

Array.implement({

    logA: function(){},

    logB: function(){}

});

In this example, we declared the callback function to be a mirror for Array. For every item processed by implement, the callback function will be invoked and passed two arguments. The first argument, name, is the key of the item being implemented and the second argument, method, is the actual function. For this code, the callback function will be invoked first with the arguments 'logA' and a function, and then with 'logB' and another function.

Function mirrors are especially useful in cases when you want to process the method first before implementing it on another type. For example, the MooTools Elements type uses a function mirror to transform methods from Element into methods that work on an array-like object. We'll learn more about this particular trick in Chapter 8 on the Element type.

The extend Method and Generics

We first saw the use of the extend method in the chapter on class extras. This method is inherited from Function.prototype, and allows us to augment function objects directly:

var fn = function(){};

console.log(fn.prop); // undefined
console.log(fn.item); // undefined

fn.extend({

    prop: 'property',

    item: 'item'

});

console.log(fn.prop); // 'property'
console.log(fn.item); // 'item'

Instead of declaring the properties prop and item directly using an assignment statement, we passed an object to the extend method of fn containing these new properties. The result of using extend is the same as a normal assignment, but it's cleaner and more organized.

Classes, being abstracted constructor functions, also inherit the extend method from Function.prototype. Type objects, on the other hand, also have an extend method, but it's an overriding method that's inherited from Type.prototype. The original extend method from Function.prototype and the one from Type.prototype have the same external API—they both accept an object argument and they both perform the same extension operation using direct augmentation. Type.prototype.extend has an additional feature that's not present in its Function.prototype counterpart—but we'll talk about this in the next section.

One important use of the extend method that we saw before was to add static methods to classes. We can also do this with type objects:

Array.extend({

    identity: function(){
        console.log(this === Array);
    },

    log: function(){
        console.log('Hello'),
    }

});

Array.identity(); // true
Array.log(); // 'Hello'

Here we added two static methods to Array, identity, and log. The identity method is used to show that the this keyword in the static method is bound to the type object, while the log method is a simple method that logs the string 'Hello'. Though these two are very trivial examples, they tell us that static methods work the same for type objects as they do for classes: they're actually methods of the type object itself rather than its instances.

There's a special group of static methods, though, that are particularly important in MooTools and they're called generics. A generic is a static version of an instance method that can be applied to any object. Generics are handy for using methods from one type on another type without having to directly call the method from the prototype.

The best way to explain generics is with an example. In the section on array objects in this chapter, I mentioned that array methods are intentionally generic in the sense that they can be used on any object. For example, we can use the push method of arrays to add a new item to an array-like object:

var obj = {0: 'pizza', length: 1};

console.log(obj.length); // 1

// invoke the push method of arrays
Array.prototype.push.call(obj, 'soda'),

console.log(obj.length); // 2

In order to access the push method, we had to access the method via Array.prototype. We then used the function method call, passing in the arguments obj and 'soda'. The first argument becomes the value of the this keyword inside the push method, while the second is the actual item that will be pushed into the object. The result is that the string 'soda' is pushed into our array-like object, and its length property is automatically incremented.

Now imagine that you need to use the push method on that particular object several times through your code. Accessing it via Array.prototype is unnecessarily verbose, and doing it multiple times leads to unnecessary repetition—not to mention having to use the call method, too. To really make these methods useful, we need an easier way to access them.

This is where generic methods come in. Take a look at this version of the previous example:

var obj = {0: 'pizza', length: 1};

console.log(obj.length); // 1

// invoke the push method of arrays
Array.push(obj, 'soda'),

console.log(obj.length); // 2

We replaced the original call to Array.prototype.push.call(obj, 'soda') to Array.push(obj, 'soda'). Array.push is a generic method. Using this method is the same as calling Array.prototype.push.call, and it expects the same arguments. The first argument is always the this keyword value for the method, and the arguments after that are the actual arguments for the method. The result is the same, but the actual invocation pattern is shorter than our original example.

MooTools automatically creates the generic versions of the methods of all native types. The string instance method split, for example, is available via the String.split generic, while the regexp method test is accessible via RegExp.test. All generic methods have the same invocation signature: the first argument is always the this keyword value, and the arguments after that will be the actual arguments passed to the method.

The implement method also creates generic methods automatically. Any method you add using the implement method would have a corresponding generic method:

console.log(typeOf(Array.log)); // 'null'
Array.implement({

    log: function(){
        console.log('Hello'),
    }

});

console.log(typeOf(Array.log)); // 'function'

Array.log(); // 'Hello'

Generic methods are used heavily in the MooTools framework. In fact, MooTools forgoes extending Object.prototype by implementing all additional Object methods as generics. We've already encountered some generics used inside MooTools in the previous chapters, like Array.slice and Object.merge, and we'll encounter more of them as we go along.

Another set of important static methods are the from methods, and there are four of them:

String.from takes any argument and returns the string value of the argument. The call String.from([1, 2, 3]), for example, returns '1,2,3'.

Number.from takes any argument and uses parseFloat to change the argument to a number value. It then returns that value as a number, or null if it can't be turned into a proper number.

Array.from takes any argument and returns an array. If the argument passed is already an array, it returns the argument without modification. If the argument passed is an array-like object, array.from turns it into a true array before returning it. If the argument is neither an array nor an array-like object, it returns a new array containing the argument. For example, Array.from('hello') will return ['hello'].

Function.from takes any argument and returns a function. If the argument passed is already a function, function.from returns it without modification. Otherwise, it returns a new function that returns the passed argument: Function.from('hello') will return function(){ return 'hello' }.

We already saw Array.from in Chapter 5, and we'll see it—and the other from methods—again throughout this book.

Creating New Types

One of the best things about the MooTools type system is its extensibility and openness. Not only are developers allowed to use the type system to implement new methods for native types, but we're also encouraged to make use of the publicly available Type constructor to implement our own set of custom types.

This was not always the case, of course. Prior to version 1.3 of MooTools, the inner workings of the MooTools type system were considered off-limits to developers. In older versions of the framework, the type system was implemented through the private Native constructor, which was a more complex forerunner of the Type constructor. It was one of those APIs that the experts knew about and used, but was not discussed in the documentation nor divulged to regular developers because of its private status.

However, times have changed and the Native constructor has been replaced with the simpler Type API—which is considered part of the public API. It's great that we can take full advantage of the type system now, and we'll do just that by implementing our own custom type.

A Table Type

As an example, we'll create a new type called a table. A table is a wrapper for the basic JavaScript object that supports getters and setters, as well as other utility methods. Before we actually implement it, let's see an example of its usage:

var table = new Table();

// setting values
table.set('item', 'pencil'),
table.set({'fruit': 'banana', 'person': 'shiela'});

// accessing objects
table.get('item'), // returns 'pencil'
table.get('item', 'fruit'), // returns {item: 'pencil', fruit: 'banana'}
table.get('event'), // returns undefined

// removal
table.set('event', 'birthday'),
table.get('event'), // returns 'birthday'

table.remove('event'),
table.get('event'), // returns undefined

// membership
table.hasKey('item'), // returns true
table.hasValue('banana'), // returns true
table.keyOf('pencil'), // returns 'item'

// keys and values
table.keys(); // returns ['item', 'fruit', 'person']
table.values(); // returns ['pencil', 'banana', 'shiela']
table.length(); // returns 3

// traversal
table.each(function(item, key){
    console.log(key + ': ' + item);
});

/*
    item: pencil
    fruit: banana
    person: shiela
*/

Unlike a basic object, the items in a table aren't accessible from the table object directly via dot notation, but are accessible only using the get and set methods. We'll also have a remove method for removing items from the table, and three membership methods, hasKey, hasValue, and keyOf, to check whether specific keys or values are present in the table. Finally, we have the utility methods keys, values, and length to give us more information regarding the table itself, and an each method for table traversal.

This sounds like a very complex type, but it's actually pretty simple to implement as we'll see through this section. All the utility functions we need to implement this are already available through native JavaScript functions and MooTools language additions, and creating this new type will be quite easy.

The Table Constructor

The first thing we need to consider, though, is the constructor. Unlike classes, type objects make no distinction between constructors and initializers. In fact, there are no initializers for type objects: the constructor function acts both as object constructor and initializer.

There are two main reasons why this design was implemented for the type system. First is that native type constructors don't separate constructors and initializers, so it was deemed necessary to follow this standard in the MooTools type system. The second reason is more important: it is because type constructors were created for the purpose of initializing basic data types, which should be much simpler and more lightweight than classes. If you want a full-featured object system, use a class; otherwise, use a type. (We'll talk more about these distinctions in the last part of this section).

Since the special constructor features like automatic dereferencing available to classes aren't implemented for type objects, we need to make sure we're mindful of these issues. Dereferencing is the biggest of these issues, and we have to make sure that we create any object-based property inside the constructor itself, rather than declare them in our prototype.

In our table type, we'll need one of these object properties called $storage. Since it's an object property, we need to set it inside our Table constructor:

function Table(){
    this.$storage = {};
};

This is the foundation of our table type: the Table constructor. We used a function definition rather than a declaration since we want our constructor to have a proper name property—and also since this is recommended MooTools style. Inside the constructor, we have a line that creates a $storage property for the instance, which will then be used to store the items for our table. Because $storage is an object, we set it inside the constructor rather than in Table.prototype so that each table instance will have a unique storage object.

In order to make our Table constructor similar to native types, we need to give it the ability to be called as a regular function. If you recall from earlier in this chapter, we found out that native constructors could be called as regular functions, and they either perform type casting or create new instances. We'll follow the latter: if our Table constructor is called in conjunction with the new keyword, it'll act as a regular object constructor; otherwise, it'll return a new Table instance.

function Table(){
    if (instanceOf(this, Table)){
        this.$storage = {};
    } else {
        return new Table();
    }
};

We modified our constructor by adding an instanceOf check to see if the new keyword was used. If our Table constructor was called with the new keyword, the value of this inside the function will be an instance of the Table type. On the other hand, calling Table as a regular function sets the this keyword differently, so we'll need to instantiate and return a true instance of the Table type.

Now that the constructor is ready, all we need to do is turn it into a type object, and that's as simple as passing it to the Type constructor:

function Table(){
    if (instanceOf(this, Table)){
        this.$storage = {};
    } else {
        return new Table();
    }
};
new Type('Table', Table);

var table = new Table();

console.log(typeOf(table)); // 'table'

Setter, Getter, and Removal

With the constructor done, it's now time to implement our getter and setter methods. Instead of adding these methods directly to Table.prototype, we'll use the implement method so that our code is more organized.

The methods themselves are very simple:

Table.implement({

    set: function(key, value){
        this.$storage[key] = value;
        return this;
    },

    get: function(key){
        return this.$storage[key];
    }

});

The first method is set, and it takes two arguments: a string key, which is the identifier for the item, and value, which can be any value associated with the key. Storing the value in the $storage itself is as simple as assigning the particular key to the value. The get method, on the other hand, requires only one argument, key, which is the key of the item being accessed, and returning the value from the storage is also done using the simple access expression.

However, if you look back at the example use, you'll notice that the set and get methods have two forms. In the first form, they're used like the simple declaration above: set requires two arguments, while get requires a single one. In the second form, they're used to perform multiple operations. The set method in this form requires only one argument, an object literal, and it adds all the items from this object to the storage. Meanwhile, the get method in the second form accepts multiple key arguments, and returns an object containing the results.

To implement these new forms, we can modify our methods so they'll accept different argument types. The set method would have to be modified to check the first argument: if the argument is a string, it's used as a key and the second argument is used as the value, and if it's an object, the method should loop through the object and add each item to the internal storage. The get method, on the other hand, will have to check for the number of arguments and return either a single value or an object containing multiple key-value pairs depending on the number of key arguments passed.

It sounds like a very complicated task to add these additional features. Fortunately, MooTools already has it covered with two function decorators we can use to transform our set and get methods. The first is the overloadSetter decorator, which takes a function with the signature fn(key, value) and gives it the ability to accept object literal arguments, such as fn({key: value}). The second decorator, overloadGetter, takes a function with the signature fn(key) -> value, and turns it into a function that can accept the signature fn(key1, key2, ...) -> {key1: value1, key2: value2, ...}.

These two decorators are implemented as function methods, and using them for our functions is as simple as invoking them in our declaration:

Table.implement({

    set: function(key, value){
        this.$storage[key] = value;
        return this;
    }.overloadSetter(),

    get: function(key){
        return this.$storage[key];
    }.overloadGetter()

});

Just by adding these invocations, we've transformed our set and get methods into methods that take different argument types. With this declaration, our methods are now usable in the two forms we saw in the initial example.

Finally, we add the remove method, which deletes items from the storage:

Table.implement({

    remove: function(){
        var storage = this.$storage;
        Array.from(arguments).each(function(key){
            delete storage[key];
        });
    }

});

Like our decorated get method, the remove method can take multiple key arguments. Unlike get, though, we didn't decorate remove but instead used very simple code to implement the method. First, we created a local variable storage so we could access the internal storage object from inside our callback function. We then used the Array.from generic to transform the arguments object into a true array, and finally we looped through each of the arguments using the each method and deleted them from the storage. The result is a cleanly implemented remove method that accepts multiple arguments.

Membership Methods

Next on our list of methods are the three membership methods: keyOf, hasValue, and hasKey. The keyOf method takes a single argument, value, and returns the key associated with that particular value, while the hasValue and hasKey methods check whether a particular value or key exists in the table.

Let's implement the easiest one first: hasKey. If you recall our discussion on objects, we learned that if we try to access a nonexistent property of an object, we'll get the undefined value. We can therefore implement hasKey simply by checking whether a particular key in our storage object is equal to the undefined value:

Table.implement({

    hasKey: function(key){
        return this.$storage[key] !== undefined;
    }

});

The next two methods, keyOf and hasValue, are trickier. Since they operate on values rather than keys, we have no way to directly access them through our storage object. What we need to do is to use a traversal loop to go through each of the items in our storage and compare their value with the argument.

The hasValue method is easier to implement, since we'll simply have to check if the particular value exists in our storage object, regardless of which key is associated with it:

Table.implement({

    hasValue: function(value){
        var storage = this.$storage;
        for (var key in storage){
            if (storage[key] === value) return true;
        }
        return false;
    }

});

Inside our hasValue method, we loop through each item in the storage object using a for-in loop. For every item we go through, we compare the value of the item with the value argument passed to our method. If we get a match, we immediately halt execution of the function by returning true. If the loop finishes without finding any matches, we fall back to a return value of false.

The keyOf method is similar to the hasValue method, but instead of returning a boolean, we return the actual key associated with the object, or null if there's no such key:

Table.implement({

    keyOf: function(value){
        var storage = this.$storage;
        for (var key in storage){
            if (storage[key] === value) return key;
        }
        return null;
    }

});

Keys, Values and Traversals

We're almost done with our Table type, but we need to implement a couple more methods. Thankfully, they're all similar to our previous item-traversing functions, so it will be very easy to implement them.

Two of the methods we still need to implement are keys and values, which deal with the keys and the values as groups. The keys method returns all the keys of the items in a table, while the values method returns the values of these items:

Table.implement({

    keys: function(){
        var storage = this.$storage;
        var results = [];
        for (var key in storage) results.push(key);
        return results;
    },
values: function(){
        var storage = this.$storage;
        var results = [];
        for (var key in storage) results.push(storage[key]);
        return results;
    }

});

We introduce a new variable in these methods called results, which is an array the keys and values are pushed into. Like hasValue or keyOf, both methods require a traversal loop to iterate through the items in the storage object. For each pass of the traversal loop, the methods push the current key or value into the results array. At the end of the methods, we return the results array, which then contains the needed values.

For the length method, we need to create a method that returns the number of items in the table. One way to do this is through another traversal loop, with an accumulator variable that is incremented with each pass through the loop. But a much simpler way is to reuse the keys (or values) method:

Table.implement({

    length: function(){
        return this.keys().length;
    }

});

Instead of writing another traversal loop for length, we simply invoked the keys method to retrieve an array of keys and then return the length of the array. This shortcut is possible since the number of keys in the internal storage would be equal to the number of items it contains.

Finally, we need to implement the last method: each. Like the array method of the same name, the each method of our Table type accepts a callback function that will be invoked for every item in the table as well as an optional bind argument that will be the this keyword value for the callback function. The callback function will receive three arguments when invoked: the current value, the current key, and the table itself.

Table.implement({

    each: function(fn, bind){
        var storage = this.$storage;
        for (var key in storage) fn.call(bind, storage[key], key, this);
        return this;
    }

});

Table.alias('forEach', 'each'),

As with our previous methods, we use a for-in loop to traverse our storage object. For each item in our storage, we invoke the callback function fn using the call method, binding the object value of our bind argument and passing in the current value, the current key, and the table itself. Since we don't need to return any special value for this method, we simply return the table itself at the end of the function. We also add an alias, for the each method called forEach in order to conform to the native name of the method.

Our Final Type

Now that we've implemented all the necessary methods for our Table type, let's combine all our snippets into our final code:

function Table(){
    if (instanceOf(this, Table)){
        this.$storage = {};
    } else {
        return new Table();
    }
};

new Type('Table', Table);

Table.implement({

    set: function(key, value){
        this.$storage[key] = value;
        return this;
    }.overloadSetter(),

    get: function(key){
        return this.$storage[key];
    }.overloadGetter(),

    remove: function(){
        var storage = this.$storage;
        Array.from(arguments).each(function(key){
            delete storage[key];
        });
    },

    hasKey: function(key){
        return this.$storage[key] !== undefined;
    },

    hasValue: function(value){
        var storage = this.$storage;
        for (var key in storage){
            if (storage[key] === value) return true;
        }
        return false;
    },

    keyOf: function(value){
        var storage = this.$storage;
        for (var key in storage){
            if (storage[key] === value) return key;
        }
        return null;
    },

    keys: function(){
var storage = this.$storage;
        var results = [];
        for (var key in storage) results.push(key);
        return results;
    },

    values: function(){
        var storage = this.$storage;
        var results = [];
        for (var key in storage) results.push(storage[key]);
        return results;
    },

    length: function(){
        return this.keys().length;
    },

    each: function(fn, bind){
        var storage = this.$storage;
        for (var key in storage) fn.call(bind, storage[key], key, this);
        return this;
    }

});

Table.alias('forEach', 'each'),

It looks good, right? Using what we've learned about the Type constructor and type objects, we were able to implement a totally new object type. The simplicity of the MooTools type system is apparent in this sample code: our code is clean and organized, the API itself is easy to follow and understand, and the results are awesome enough to warrant real-life usage.

There is, of course, room for improvement. Our Table type is fine as it is, but still has some issues that need fixing. A particular improvement that I want you to figure out is how to limit items to only those that are directly defined through set. For example, calling table.get('constructor') will always return a value, even if we never set a 'constructor' item. This is because our internal storage object inherits several properties and methods from Object.prototype, and these members are also accessible to our methods. The easiest way to solve this would be to add checks using hasOwnProperty in our methods—and I'll leave it to you to fix the code accordingly.

The Wrap-Up

In this chapter, we learned about the native JavaScript type system and its MooTools counterpart. We learned what a type is exactly, as well as how JavaScript handles native types. We went through the various native types that JavaScript implements, and the rules that govern their transformation from one type to the other.

We also learned about the components of the MooTools type system and type objects in particular, which are class-like objects that are used to manage and enhance native types. We discovered how we can easily extend native types, and we topped it off by implementing our own custom type

This chapter certainly discusses quite a handful of topics, and I hope it is a fitting finale for part one of this book. We've gone through a lot of stuff in the past six chapters, from functions and objects to classes and types, and I expect that, at this point, you've already acquired the knowledge you'll need to understand much more complicated JavaScript code.

If your understanding is still a bit shaky, though, I advise you to reread this and the previous chapters. We're going to start exploring more complicated terrain in the next chapters, and unless you're comfortable with what we've already studied, you might have a hard time understanding parts two and three of this book.

But if you think that you're ready to trek onward, better put on your hiking boots because we're about to explore exotic lands crafted outside the ECMAScript specification.

Ready? Then turn the page so we can begin our exploration of JavaScript in the browser.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset