Chapter 6. Functions and Arrays

In the previous chapter, you learned about inheritance and saw how to pass members onto child objects using classical, prototypal, deep copy, and mixin inheritance. As I noted at the end of that chapter, it's actually functions that are more often passed on to child objects. This is because common processes, as provided by a function, are frequently more useful than common data, as provided by members. In this chapter, I'll cover why you would want to use functions and how to take advantage of function inheritance. There are a lot of cool tricks to learn in this area and we'll take advantage of a lot of them in later chapters, as you'll see from the copious forward references. In other words, this is quite an important chapter.

In addition to the function subtype, arrays are a second subtype of the object value type. Arrays are special primarily due to the predefined methods they inherit from Array.prototype. We'll explore those methods in this chapter.

Why Use Functions?

Ben & Jerry's, Häagen-Dazs, and other French-style ice creams are made by creating a satiny custard from cream, milk, and egg yolks. Compared to Philadelphia-style ice cream, which is made by simply churning cream and milk, French-style ice cream can be tricky for a beginner to make. So, it's best to start with vanilla before gilding the lily. Err, gilding the orchid—vanilla flavoring derives from the fruit of the vanilla orchid.

Anyway, the most delicious vanilla ice cream is made by steeping the pod and seeds from a vanilla bean rather than by stirring in vanilla extract, which is less flavorful. Vanilla beans differ in taste depending on where the vanilla orchids are grown. Madagascar Bourbon are my favorite. Compared to those, Tahitian are milder in flavor, and Mexican are bolder.

If you are an ice-cream beginner, take your lumps with the following recipe for French vanilla before gilding the orchid with chocolate, fruit, and so on:

1 cup, Organic Valley heavy whipping cream
2 cups, Organic Valley half & half
5/8 cup, sugar
6 egg yolks
1 Madagascar Bourbon vanilla bean
  • Separate the egg yolks into mixing bowl.

  • Slit the vanilla bean length-wise with a paring knife.

  • Scrape the tiny, pasty seeds into a saucepan. Then add the empty pod halves—most of the vanilla flavor derives from the pod.

  • Add 2 cups half & half, 1/2 cup heavy whipping cream, and 5/8 cup sugar to saucepan containing the vanilla seeds and pod halves.

  • Add the remaining 1/2 cup heavy whipping cream to the 6 egg yolks and whisk vigorously until smooth.

  • Stirring frequently with wooden spoon, put the saucepan over medium heat for 4 minutes or just until the liquid begins to ripple but do not boil.

  • Temper the egg yolks by gradually adding 1/4 of the hot liquid from the saucepan while whisking continuously. Tempering prevents the yolks from curdling, which would ruin the custard.

  • Pour the tempered yolk mixture into the saucepan while whisking constantly.

  • Stirring constantly with wooden spoon, put the saucepan over medium heat for 4 minutes or until custard thickens at roughly 170 F—do not boil.

  • Pressing with the wooden spoon, strain the custard through a fine mesh sieve into a bowl to remove the vanilla pod, seeds, and any custard lumps.

  • Stirring occasionally, cool the custard by placing the bowl into a larger bowl halfway filled with ice water.

  • Remove the custard from ice bath and chill in refrigerator for at least a few hours if not overnight as well-chilled custard freezes into ice cream more effectively.

  • Finally, churn chilled custard in ice-cream maker and then freeze for a few hours or until firm.

Once you've mastered French vanilla, it's easy to embellish it, as many flavors derive from it. For example, to make coffee ice cream, just steep 1/4 cup coarsely ground espresso beans with the vanilla pod and seeds. Then strain away the espresso grinds along with the vanilla pod and seeds and any custard lumps with a fine mesh sieve. Or to make chocolate, whisk 3/8 cup Dutch process cocoa—I recommend Callebaut—into the yolks and cream prior to tempering.

The French-style ice-cream recipes for vanilla, coffee, and chocolate displayed here are similar:

1 cup, Organic Valley heavy whipping cream
2 cups, Organic Valley half & half
5/8 cup, sugar
6 egg yolks
1 Madagascar Bourbon vanilla bean

1 cup, Organic Valley heavy whipping cream
2 cups, Organic Valley half & half
5/8 cup, sugar
6 egg yolks
1/4 cup, Starbucks Espresso beans, freshly and coarsely ground
1 Madagascar Bourbon vanilla bean

1 cup, Organic Valley heavy whipping cream
2 cups, Organic Valley half & half
5/8 cup, sugar
6 egg yolks
3/8 cup, Callebaut cocoa
1 Madagascar Bourbon vanilla bean

In JavaScript, if you find yourself writing a certain sequence of statements over and over that differ just slightly as the steps in our recipes for vanilla, coffee, and chocolate ice cream do, then you would want to create a function for those statements. Then define parameters for the differences. The constructor and helper functions in Chapter 5 such as CherryGarcia() and extend() are good examples of this.

You already know when to create a function. Therefore, in this chapter, we'll explore two vital features of JavaScript functions that make them distinctive. First, functions are values that may be expressed with literal notation. In geeky terms, this means JavaScript has first-class functions. Second, JavaScript has function scope, which makes functions vital for variable lookup.

Open firebug.html in Firefox, and then press F12 to enable Firebug—if you're just joining us, flip back to the preface for details on how to do this—and let's begin exploring functions as values.

Note

If you save a function to an object, the function is referred to as a method.

Functions Are Values

First-class functions may be expressed with literals. Typically, those are unnamed or anonymous. You invoke a function through the variable, member, or element you saved it to. For example, in Chapter 5, we saved an unnamed function expression to a variable named WildMaineBlueberry and invoked the anonymous function by way of the variable's name, WildMaineBlueberry:

var WildMaineBlueberry = function (blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"]
};
var wildMaineBlueberry = new WildMaineBlueberry();
console.dir(wildMaineBlueberry);

Named function values can be created with a function declaration. In turn, you can invoke the function by its name. So, rather than save a function expression to a variable named WildMaineBlueberry, we could create a function named WildMaineBlueberry like so:

function WildMaineBlueberry (blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
}
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"]
};
var wildMaineBlueberry = new WildMaineBlueberry();
console.dir(wildMaineBlueberry);

What's the difference? For one thing, functions declarations cannot be assigned to variables, members, or elements. That is to say, you have to declare the function and then assign its name to a variable, member, or element. It's two steps rather than one. Moreover, you cannot pass a function declaration to a function. Rather, you have to pass its name. Again, it's two steps rather than one. Finally, declarations create functions that may be called prior to being defined, though doing so is frowned upon. For those reasons and so that you get used to using functions as values just like objects or booleans, we'll continue using function expressions rather than declarations for the remainder of our journey.

Function Members

Functions are values of the object value type, so they inherit members from Object.prototype like valueOf() as well as the following ones from Function.prototype. Note that Function.prototype.constructor and Function.prototype.toString() override Object.prototype.constructor and Object.prototype.toString().

constructor
length
apply()
bind()
call()
toString()

Function.prototype.constructor just refers to Function(), length contains the number of named parameters defined for the function, and toString() contains the function definition as a string. Query those for WildMaineBlueberry(). Note that we'll explore apply(), bind(), and call() in just a bit.

function WildMaineBlueberry (blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
}
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"]
};
WildMaineBlueberry.constructor;
// Function()
WildMaineBlueberry.length;
// 2
WildMaineBlueberry.toString()
// "function WildMaineBlueberry(blueberries, vanilla) { this.blueberries = [2, "cup",
blueberries ? blueberries : "fresh wild Maine blueberries"]; this.vanilla = [1, "bean",
vanilla ? vanilla : "Madagascar Bourbon"]; }"

Tip

It's worth noting that JavaScript initializes an element in the arguments object for every named or unnamed parameter. You can use this object to obtain the number of parameters used (arguments.length) or get the name of the function that called the current function (arguments.callee), which we will cover a bit later in the "Recursion" section. You can also obtain parameters by using their place in the parameter list (for example, arguments[0] to obtain the first parameter).

Conditional Advance Loading

One implication of functions being values is that you can conditionally choose one of two or more values for a function as your script loads. This technique, referred to as conditional advance loading or load-time branching, is one we'll turn to quite a bit in Chapters 9 and 10.

Conditional advance loading can be used to choose a value for a function relative to what ECMAScript or DOM features a browser's JavaScript interpreter implements. This means we set the function's value to use a particular feature if it is available or write that particular feature in the function's value if it is not available. For example, ECMAScript 5 defines the following 12 new methods. Note that those are static methods, which is to say they are saved to Object, not to Object.prototype.

Object.create()
Object.defineProperty()
Object.defineProperties()
Object.getOwnPropertyDescriptor()
Object.keys()
Object.getOwnPropertyNames()
Object.preventExtensions()
Object.isExtensible()
Object.seal()
Object.isSealed()
Object.freeze()
Object.isFrozen()

Right now, no browser supports these methods, but the JavaScript interpreters in Explorer 9 and Firefox 4 will do so. However, those methods will greatly improve JavaScript's inheritance features. So, if available, we'd want to work those into the functions we wrote in Chapter 5.

One way to do so is with conditional advance loading. The "conditional" bit means an if conditional statement or ?: conditional expression that checks to see whether a particular feature is available, while the "advance loading" bit means doing the feature detection as the script loads, which is to say in advance.

So jargon in hand, let's rewrite extend() from Chapter 5 so that Explorer 9 and Firefox 4 can use the following ECMAScript 5 methods, each of which I will explain as I show you how to implement them:

Object.create()
Object.defineProperty()
Object.defineProperties()

The first thing we want to do is define methods not quite unlike the ECMAScript 5 ones for current versions of Explorer, Firefox, Safari, and Opera; in other words, we're going to write our own versions of create(), defineProperty(), and defineProperties() for browsers to use if they don't support ECMAScript5. Let's begin by roughing out an if condition for missing members. Do you remember what the value of a missing member is?

Yup, undefined.

Thus far, we have this:

if (Object.defineProperty === undefined) {
}
if (Object.defineProperties === undefined) {
}
if (Object.create === undefined) {
}

Now, within the empty if blocks, create the missing members and just assign an empty function literal. Take care not to omit the semicolon following each assignment statement:

if (Object.defineProperty === undefined) {
  Object.defineProperty = function () {
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function () {
  };
}
if (Object.create === undefined) {
  Object.create = function () {
  };
}

Writing Object.defineProperty()

With Object.defineProperty(), you can assign a value to a member as well as define whether a member is writable, enumerable, or deletable.

Note

Enumerable means that a member is enumerated in a for in loop, while writable simply means we can assign a value to the member. So, you can see that an array is enumerable, as are the properties of an object.

Prior to ECMAScript 5, any member you added to an object was writable, enumerable, and deletable. For most members, that's what you want. So, Object.defineProperty() is not a replacement for adding members with the ., [], and = operators. Rather, it is just for doing things such as ensuring that members like constructor (for a prototype object) don't appear in a for in loop.

To do so, you pass Object.defineProperty() three parameters.

  • First, an object to add a member to or that contains a member you want to modify.

  • Second, the name of a member, as a string, that you want to add or modify.

  • Third, a data descriptor or accessor descriptor object. Data descriptors may contain the following four members. Minimally, a data descriptor must contain a value or writable member.

    • value contains the member's value, which defaults to undefined.

    • writable contains a boolean indicating whether the member's value is writable. true means that it is, and false, which is the default, means that it is not. So, false means that you cannot assign a new value to a member with the = operator.

    • configurable contains a boolean indicating whether a member may be deleted and whether its descriptor attributes are writable, with the exception of writable, which is carved in stone. false, the default, means that a member may not be deleted and that its configurable and enumerable attributes may not be changed. true means the inverse.

    • enumerable contains a boolean indicating whether the member would be enumerated in a for in loop. true means that it would be, and false, the default, means that it would not. So, enumerable provides a way to ensure a member such as constructor doesn't appear in a for in loop.

Note that the writable, configurable, and enumerable descriptor attributes default to false. On the other hand, for a member traditionally created or modified with the = operator, writable, configurable, and enumerable default to true. That's your clue to continue using = for most assignment operations. Note that, if you add a member with the =, you may later change its writable, configurable, and enumerable attributes with Object.defineProperty().

Note

I will not cover accessor descriptors in this book. You use them to provide a function that is called whenever a property value is accessed or set.

For browsers that do not implement Object.defineProperty(), we'd simply want to assign the descriptor's value member with the = operator and disregard the writable, configurable, and enumerable members. Note that, if the descriptor just has a writable member, descriptor.value evaluates to undefined.

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function () {
  };
}
if (Object.create === undefined) {
  Object.create = function () {
  };
}

Writing Object.defineProperties()

Object.defineProperties() is sort of the plural version of Object.defineProperty(). That is to say, it can create or modify more than one member. Unlike Object.defineProperty(), this one takes two parameters.

  • The first one is the same as Object.defineProperty()—the object on which to add or modify members.

  • The second one is an object containing one or more descriptor objects.

So, for pre-ECMAScript 5 browsers, we'll loop through the descriptors parameter with a for in loop, disregarding descriptor members other than value:

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function () {
  };
}

Writing Object.create()

Finally, Object.create() works with two parameters (recall we discussed create() in Chapter 5).

  • The first one is the object to inherit members from.

  • The optional second one is an object containing descriptors of its own members to add to the child object.

For pre-ECMAScript 5 browsers, we'll write a function similar to clone() in Chapter 5. In the event that the optional descriptors parameter is defined, we'll pass those to Object.defineProperties():

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}

Using the new Functions

Now let's invoke toString() on Object.defineProperty, Object.defineProperties, and Object.create and then pass the string to Firebug's console.log() method. If you're running Firefox 3, JavaScript will print our pre-ECMAScript 5 functions in the left panel of Firebug, as in Figure 6-1. On the other hand, if you're running Firefox 4, JavaScript will print the native ECMAScript 5. Note that native functions are written in a compiled language like C++, so rather than print compiled gobbledygook for the body of a native function, JavaScript simply prints [native code]:

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
console.log(Object.defineProperty.toString());
console.log(Object.defineProperties.toString());
console.log(Object.create.toString());
Firefox 3 opts for our pre-ECMAScript 5 functions.

Figure 6-1. Firefox 3 opts for our pre-ECMAScript 5 functions.

Now with those conditional advance loaders written, we can rework extend() from Chapter 5 so that the constructor members we add to the child and parent prototype objects are not enumerated in a for in loop. That is to say, our constructor members will behave like the native ones that get overwritten during prototype chaining or prototype replacement. Moreover, we'll set writable to true and configurable to false so that the constructor member can be changed but not deleted. Finally, we'll make the child constructor's superclass member writable and configurable but not enumerable. In this way, we retain the option to have the child not inherit from a parent.

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
};
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
var extend = function (child, parent, descriptors) {
  child.prototype = Object.create(parent.prototype, descriptors);
  Object.defineProperty(child.prototype, "constructor", {
    value: child,
    writable: true,
    enumerable: false,
    configurable: false
   });
  if (! parent.prototype.hasOwnProperty("constructor")) {
    Object.defineProperty(parent.prototype, "constructor", {
      value: parent,
      writable: true,
      enumerable: false,
      configurable: false
    });
  }
  Object.defineProperty(child, "superclass", {
    value: parent.prototype,
    writable: true,
    enumerable: false,
    configurable: true
   });
};

Insofar as writable, enumerable, and configurable default to false, we can more succinctly write extend() like so. Just be sure to remove the comma following the final descriptor member. After all, descriptors are just object literals, so those must abide by object literal notation.

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
}
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
var extend = function (child, parent, descriptors) {
  child.prototype = Object.create(parent.prototype, descriptors);
  Object.defineProperty(child.prototype, "constructor", {
    value: child,
    writable: true
   });
  if (! parent.prototype.hasOwnProperty("constructor")) {
    Object.defineProperty(parent.prototype, "constructor", {
      value: parent,
      writable: true
     });
  }
  Object.defineProperty(child, "superclass", {
    value: parent.prototype,
    writable: true,
    configurable: true
   });
};

Now let's rework the extend() sample from Chapter 5 in which we had CherryGarcia() inherit from Strawberry. In ECMAScript 5 compliant browsers like Explorer 9 and Firefox 4, Strawberry.prototype.constructor and CherryGarcia.prototype.constructor will not be enumerated in a for in loop or deleted by the delete operator. Moreover, CherryGarcia.superclass will not be enumerated in a for in loop but would be deleted by the delete operator. On the other hand, in pre-ECMAScript 5 browsers extend() would create constructor and superclass members by simple assignment with the = operator. So, Firebug's console.dir() method, which prints an object's enumerable members, would print the constructor member for strawberry and cherryGarcia if you're running Firefox 3 as in Figure 6-2, but not if you're running Firefox 4.

if (Object.defineProperty === undefined) {
  Object.defineProperty = function (obj, name, descriptor) {
    obj[name] = descriptor.value;
  };
}
if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
}
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
var extend = function (child, parent, descriptors) {
  child.prototype = Object.create(parent.prototype, descriptors);
  Object.defineProperty(child.prototype, "constructor", {
    value: child,
    writable: true
   });
  if (! parent.prototype.hasOwnProperty("constructor")) {
    Object.defineProperty(parent.prototype, "constructor", {
      value: parent,
      writable: true
     });
  }
  Object.defineProperty(child, "superclass", {
    value: parent.prototype,
    writable: true,
    configurable: true
   });
};

var Strawberry = function(strawberry) {
  this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"];
};
Strawberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [3],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var CherryGarcia = function(cherry, bittersweet) {
  this.cherries = [2, "cup, pitted and halved", cherry ? cherry : "Bing"];
  this.bittersweet = [1, "cup, coarsely chopped", bittersweet ? bittersweet : "Callebaut"];
};
extend(CherryGarcia, Strawberry, {
    sugar: {
      value: [9/16, "cup"],
writable: true,
      enumerable: true,
      configurable: true
    }});
var strawberry = new Strawberry();
var cherryGarcia = new CherryGarcia();
console.dir(strawberry);
console.dir(cherryGarcia);
strawberry and cherryGarcia will have enumerable constructor members in pre-ECMAScript 5 browsers.

Figure 6-2. strawberry and cherryGarcia will have enumerable constructor members in pre-ECMAScript 5 browsers.

Lazy Loading

Another implication of functions being values is that you can conditionally change a function value while it's running. This technique, referred to as lazy loading or lazy definition, is one we'll turn to often in Chapters 9 and 10.

Because native functions such as Object.create() are compiled into gobbledygook, they run much faster than your plain-text functions. So, it's best to opt for a native function to do some work if one is available. Conditional advance loading is one way to ensure that JavaScript opts for fast-running gobbledygook. Lazy loading—that is, having a function redefine itself the first time it's called—is another way.

Lazy loaders are appropriate for functions that may not be needed or that are not needed right away. Lazy refers to not redefining a function unless or until you have to. On the other hand, conditional advance loading is appropriate for functions you definitely need, especially those that are needed right away.

In Chapter 5, we wrote the following clone() function to implement prototypal inheritance:

var clone = function (donor) {
  var Proxy = function () {};
  Proxy.prototype = donor;
  return new Proxy();
};

If you omit its optional second parameter, Object.create() does the same thing as clone() but much faster. Insofar as descriptors are too unwieldy to add members that are writable, enumerable, and configurable, which is to say like those added with the = operator, more often than not you'll be omitting the second parameter. With this in mind, let's rework clone() into a lazy loader that opts for Object.create() in Explorer 9, Firefox 4, and other ECMAScript 5-savvy browsers.

Begin by putting our definition of clone in the else clause of an if condition that determines whether Object.create is defined. However, omit the var keyword because we want to overwrite the containing clone() function, not create a nested clone() function.

var clone = function (donor) {
  if (Object.create !== undefined) {
  } else {
    clone = function (donor) {
      var Proxy = function () {};
      Proxy.prototype = donor;
      return new Proxy();
    };
  }
};

Now, within the if clause, simply return the empty object created by passing donor to Object.create():

var clone = function (donor) {
  if (Object.create !== undefined) {
    clone = function (donor) {
      return Object.create(donor);
    };
  } else {
    clone = function (donor) {
      var Proxy = function () {};
      Proxy.prototype = donor;
return new Proxy();
    };
  }
};

Unfortunately, right now clone() would simply redefine itself the first time we call it. That is, it would return undefined rather than an empty object that inherits members from donor.

Hmmm.

What to do?

I know. Following the ifelse statement, we'll pass donor from the old clone() to the new clone():

var clone = function (donor) {
  if (Object.create !== undefined) {
    clone = function (donor) {
      return Object.create(donor);
    };
  } else {
    clone = function (donor) {
      var Proxy = function () {};
      Proxy.prototype = donor;
      return new Proxy();
    };
  }
  clone(donor);
};

But that will still return undefined the first time we call clone() inasmuch as a function that does not explicitly return a value with a return statement will implicitly return undefined. With this in mind, how would we fix our lazy loader?

Yup, have the old clone() explicitly return the value of passing its donor parameter to the new clone(). Note that if you are redefining a function that does not explicitly return a value, such as the thwart() or burst() functions we'll write in Chapter 9, then you can omit the return keyword.

var clone = function (donor) {
  if (Object.create !== undefined) {
    clone = function (donor) {
      return Object.create(donor);
    };
  } else {
    clone = function (donor) {
      var Proxy = function () {};
      Proxy.prototype = donor;
      return new Proxy();
    };
  }
  return clone(donor);
};

Now let's create some Ben & Jerry's Chunky Monkey with our lazy loading clone() just as we did in Chapter 5, before verifying our work with Figure 6-3. If you're running Firefox 4, then chunkyMonkey is churned by Object.create(). But if you're running Firefox 3, then chunkyMonkey is churned by Proxy(). It's delicious either way, but you'll have a scoop sooner if Object.create() is doing the churning.

var clone = function (donor) {
  if (Object.create !== undefined) {
    clone = function (donor) {
return Object.create(donor);
    };
  } else {
    clone = function (donor) {
      var Proxy = function () {};
      Proxy.prototype = donor;
      return new Proxy();
    };
  }
  return clone(donor);
};

var banana = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [9/16, "cup"],
  yolks: [3],
  banana: [1 + 1/2, "cup, puréed"],
  coconutMilk: [1/4, "cup"],
  lemon: [2, "tsp", "freshly juiced Meyer lemon"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var chunkyMonkey = clone(banana);
chunkyMonkey.walnuts = [3/4, "cup, coarsely chopped"];
chunkyMonkey.bittersweet = [1, "cup, coarsely grated", "Callebaut"];
console.dir(banana);
console.dir(chunkyMonkey);
Firefox 4 creates chunkyMonkey with Object.create(), while Firefox 3 creates chunkyMonkey with Proxy().

Figure 6-3. Firefox 4 creates chunkyMonkey with Object.create(), while Firefox 3 creates chunkyMonkey with Proxy().

Recursion

Whereas function literal expressions create function values, function invocation expressions create values of any type, typically by manipulating one or more values referred to as parameters or arguments. Insofar as a function value can self-invoke, a function can do work on parameters and then pass those back to itself. Doing so, referred to as recursion, provides a way to do a lot of mind numbing work in small steps. Recursive functions are invaluable for traversing the DOM, something we'll explore in Chapter 7. But we've already written a recursive function in Chapter 5. There we churned a quart of Ben & Jerry's Coffee Heath Bar Crunch from Vanilla Heath Bar Crunch by simply cloning members with a recursive function named cloneMembers() like so:

var cloneMembers = function cloneMembers (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    if (donor.hasOwnProperty(m)) {
      if (typeof donor[m] === "object" && donor[m] !== null) {
        donee[m] = typeof donor[m].pop === "function" ? [] : {};
        cloneMembers(donor[m], donee[m]);
      } else {
        donee[m] = donor[m];
      }
    }
}
  return donee;
};
var vanillaHeathBarCrunch = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  heathBars: [4, "bars, coarsely chopped"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.coffee = [1/4, "cup, coarsely ground", "Starbucks Espresso"];

Note how we test whether an object is an array by checking if it has a pop() method. We'll cover arrays and their methods later in this chapter, but for now remember that arrays have a pop() method of type function.

If we rewrote cloneMembers() as a nonrecursive function, which is to say deleted cloneMembers(donor[m], donee[m]);, then we'd have to invoke cloneMembers() eight times rather than one time. So, recursion spares us from having to key in the following, which as Figure 6-4 displays still works fine.

var cloneMembers = function cloneMembers (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    if (donor.hasOwnProperty(m)) {
      if (typeof donor[m] === "object" && donor[m] !== null) {
        donee[m] = typeof donor[m].pop === "function" ? [] : {};
      } else {
        donee[m] = donor[m];
      }
    }
  }
  return donee;
};
var vanillaHeathBarCrunch = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  heathBars: [4, "bars, coarsely chopped"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);

coffeeHeathBarCrunch.heavyCream = cloneMembers(vanillaHeathBarCrunch.heavyCream,
  coffeeHeathBarCrunch.heavyCream);
coffeeHeathBarCrunch.halfHalf = cloneMembers(vanillaHeathBarCrunch.halfHalf,
  coffeeHeathBarCrunch.halfHalf);
coffeeHeathBarCrunch.sugar = cloneMembers(vanillaHeathBarCrunch.sugar,
  coffeeHeathBarCrunch.sugar);
coffeeHeathBarCrunch.yolks = cloneMembers(vanillaHeathBarCrunch.yolks,
  coffeeHeathBarCrunch.yolks);
coffeeHeathBarCrunch.heathBars = cloneMembers(vanillaHeathBarCrunch.heathBars,
coffeeHeathBarCrunch.heathBars);
coffeeHeathBarCrunch.vanilla = cloneMembers(vanillaHeathBarCrunch.vanilla,
  coffeeHeathBarCrunch.vanilla);
coffeeHeathBarCrunch.heavyCream = cloneMembers(vanillaHeathBarCrunch.heavyCream,
  coffeeHeathBarCrunch.heavyCream);
coffeeHeathBarCrunch.coffee = [1/4, "cup, coarsely ground", "Starbucks Espresso"];
console.dir(vanillaHeathBarCrunch);
console.dir(coffeeHeathBarCrunch);
Recursion prevents our having to invoke cloneMembers() eight times rather than one time.

Figure 6-4. Recursion prevents our having to invoke cloneMembers() eight times rather than one time.

Don't know about you, but I'd rather work smart than hard. So, recursion is a keeper. Note that in Chapter 7 we'll write a recursive function named traverseTree() to traverse the DOM tree. Doing so possibly prevents our having to invoke traverseTree() hundreds of times by hand.

Another way to implement recursion is by way of arguments.callee, which refers to the running function. We'll use this approach a bit more later in the book:

var cloneMembers = function cloneMembers (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    if (donor.hasOwnProperty(m)) {
if (typeof donor[m] === "object" && donor[m] !== null) {
        donee[m] = typeof donor[m].pop === "function" ? [] : {};
        arguments.callee(donor[m], donee[m]);
      } else {
        donee[m] = donor[m];
      }
    }
  }
  return donee;
};
var vanillaHeathBarCrunch = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  heathBars: [4, "bars, coarsely chopped"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.coffee = [1/4, "cup, coarsely ground", "Starbucks Espresso"];

Borrowing Methods with apply() or call()

Insofar as functions are values of the object value type, they inherit members from Object.prototype and Function.prototype by way of a prototype chain in the same way as our cherryGarcia object inherits members from Object.prototype, Strawberry.prototype, and CherryGarcia. Two methods inherited from Function.prototype, apply() and call(), provide a way to borrow function values. These functions mean that you can define a function in one object and use it in another as if it were inherited, without having to inherit the whole object. In both cases, you pass the object that is inheriting the function as the first parameter (which becomes this in the inherited function), followed by either of these:

  • The arguments to the function as a series of values separated by commas, in the case of call()

  • The arguments to the function as an array, in the case of apply()

ECMAScript 5 defines an Array.isArray() method to verify whether or not a value is an array. This is a much-needed addition to JavaScript inasmuch as typeof returns "object" for an object, array, or null, and the instanceof operator does not work with frames in some Explorer versions. Let's see how call() and apply() can help.

Overriding toString()

So, for Explorer 9, Firefox 4, and other ECMAScript 5-savvy browsers, we want to verify arrayness with Array.isArray() rather than testing for an array method like pop() or slice() as we did in cloneMembers(). After all, there's no reason why an object or function could not have a method named pop() or slice().

To do so, we'd want to write a conditional advance loader for Array.isArray(). So, just like we did for Object.create(). The tricky part is writing something not quite unlike Array.isArray() for Explorer 8, Firefox, 3, and other ECMAScript 5 dummies. To do so, we're going to work with the toString() function to extract information about the object in question.

If you invoke toString() on an object that does not override Object.prototype.toString() with its own toString() method, it will return "[object Object]". For example, even though we churned wildMaineBlueberry with the WildMaineBlueberry() constructor in Chapter 5, as Figure 6-5 displays, invoking wildMaineBlueberry.toString() returns "[object Object]" inasmuch as we did not override Object.prototype.toString():

var WildMaineBlueberry = function(blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"]
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");
wildMaineBlueberry.toString();
// "[object Object]"
Invoking toString() on an object that does not override Object.prototype.toString() with its own toString() method, it will return "[object Object]".

Figure 6-5. Invoking toString() on an object that does not override Object.prototype.toString() with its own toString() method, it will return "[object Object]".

However, if we add a toString() method to WildMaineBlueberry.prototype, that will override Object.prototype.toString() as the following sample and Figure 6-6 display:

var WildMaineBlueberry = function(blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
freshLemonJuice: [2, "tsp"],
  toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");
wildMaineBlueberry.toString();
// "[object WildMaineBlueberry]"
WildMaineBlueberry.prototype.toString() overrides Object.prototype.toString().

Figure 6-6. WildMaineBlueberry.prototype.toString() overrides Object.prototype.toString().

Native JavaScript constructors defined by ECMAScript or DOM always override Object.prototype.toString(). For example, if we call toString() on the array in wildMaineBlueberry.heavyCream, JavaScript glues the elements together with commas instead of returning "[object Array]", which is what Object.prototype.toString() returns for an array when it is not overridden. Similarly, if we call toString() on the constructor function WildMaineBlueberry, JavaScript return its definition as a string. Try doing both, verifying your work with Figure 6-7.

var WildMaineBlueberry = function(blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"],
  toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");
wildMaineBlueberry.heavyCream.toString();
// "1,cup,Organic Valley"
WildMaineBlueberry.toString();
// "function (blueberries, vanilla) { this.blueberries = [2, "cup", blueberries ? blueberries
: "fresh wild Maine blueberries"]; this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"]; }"
Native JavaScript constructors defined by ECMAScript or DOM always override Object.prototype.toString().

Figure 6-7. Native JavaScript constructors defined by ECMAScript or DOM always override Object.prototype.toString().

Testing for an Array

To test for arrayness like Array.isArray() in ECMAScript 5 dummies like Explorer 8, we need to circumvent any toString() method overriding Object.prototype.toString() to avoid the gluey string that JavaScript shows us when we call toString() on an array. Having done so, we would then return true if Object.prototype.toString() returns "[object Array]" and false if not.

ECMAScript defines a couple of methods for function values, apply() and call(), that provide a way to borrow a method like Object.prototype.toString() and use it as if it were inherited (so we have access to it over the head of any overridden methods).

The first parameter to apply() or call() is an object to bind to this for the method you are borrowing. If you wanted to invoke Object.prototype.toString() on [1, "cup", "Organic Valley"], which is to say circumvent Array.prototype.toString(), you'd pass [1, "cup", "Organic Valley"] as the first parameter to apply() or call(). Try both methods, verifying your work with Figure 6-8. Note that, to save some typing, you can just replace Object.prototype with an empty object literal wrapped in parentheses.

var WildMaineBlueberry = function(blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"],
  toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");

Object.prototype.toString.apply(wildMaineBlueberry.halfHalf);
// "[object Array]"
Object.prototype.toString.call(wildMaineBlueberry.freshLemonJuice);
// "[object Array]"
({}).toString.apply(wildMaineBlueberry.blueberries);
// "[object Array]"
({}).toString.call(wildMaineBlueberry.vanilla);
// "[object Array]"
Circumventing Array.prototype.toString() to verify arrayness

Figure 6-8. Circumventing Array.prototype.toString() to verify arrayness

OK, with apply() and call() now in our noggins, we can now write a conditional advance loader as shown next. Note that it's only necessary to wrap an empty object literal in parentheses when it begins a line of code (to prevent confusion as to whether it's an object or a block), so we can omit those here. Verify a couple of values for arrayness with Array.isArray(), comparing your work to Figure 6-9. If you're running Firefox 4, JavaScript will use the native ECMAScript 5 function, but, if you're running Firefox 3, it will use our knock-off.

if (Array.isArray === undefined) {
  Array.isArray = function(v) {
    return {}.toString.apply(v) === "[object Array]";
  };
}
var WildMaineBlueberry = function(blueberries, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  freshLemonJuice: [2, "tsp"],
toString: function () { return "[object WildMaineBlueberry]";}
};
var wildMaineBlueberry = new WildMaineBlueberry("Dole frozen wild blueberries", "Tahitian");

Array.isArray(wildMaineBlueberry.halfHalf);
// true
Array.isArray(wildMaineBlueberry.halfHalf[2]);
// false
Verifying arrayness with the native Array.isArray() method or our knock-off

Figure 6-9. Verifying arrayness with the native Array.isArray() method or our knock-off

Rewriting cloneMembers()

Now that we have a better way to verify arrayness than simply groking whether a method like pop() or slice() is defined, let's rework cloneMembers() accordingly. Then try churning a quart of Coffee Heath Bar Crunch by cloning and augmenting a quart of Vanilla Heath Bar Crunch, verifying your work with Figure 6-10:

if (Array.isArray === undefined) {
  Array.isArray = function(v) {
    return {}.toString.apply(v) === "[object Array]";
  };
}
var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    if (donor.hasOwnProperty(m)) {
      if (typeof donor[m] === "object" && donor[m] !== null) {
        donee[m] = Array.isArray(donor[m]) ? [] : {};
        cloneMembers(donor[m], donee[m]);
      } else {
donee[m] = donor[m];
      }
    }
  }
  return donee;
};
var vanillaHeathBarCrunch = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  heathBars: [4, "bars, coarsely chopped"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var coffeeHeathBarCrunch = cloneMembers(vanillaHeathBarCrunch);
coffeeHeathBarCrunch.coffee = [1/4, "cup, coarsely ground", "Starbucks Espresso"];
console.dir(vanillaHeathBarCrunch);
console.dir(coffeeHeathBarCrunch);
Churning a quart of Coffee Heath Bar Crunch with an improved cloneMembers() function

Figure 6-10. Churning a quart of Coffee Heath Bar Crunch with an improved cloneMembers() function

Currying

Because both functions and their parameters are values, we can create a new function value by combining an old function and one or more parameters, so those parameters are preset in the new function. Doing so is referred to as currying in honor of its creator, Haskell Curry, who also has the Haskell programming language named after him.

ECMAScript 5 defines one new method for function values, Function.prototype.bind(), that is ideal for currying. Though Explorer 9 and Firefox 4 will implement Function.prototype.bind, as of this writing Explorer 8, Firefox, 3, and other pre-ECMAScript 5 browsers do not. So, even when the cavalry arrives, we'll still need to emulate Function.prototype.bind().

Let's roll up our sleeves and do so with the help of our conditional advance loaders for Object.defineProperties() and Object.create(). Those two conditional advance loaders were implemented with an if statement. But if isn't the only way to write a conditional advance loader—the || and ?: operators work, too. Therefore, let's choose a value for Function.prototype.bind() with the || this time.

Remember from Chapter 3 that, if the first operand to || is falsey, which is to say evaluates to "", 0, NaN, false, undefined, or null, then the overall || expression evaluates to the second operand. So, if querying Function.prototype.bind returns undefined, as it would for any pre-ECMAScript 5 browser, then our || expression will evaluate to the second operand, which will be our emulation of Function.prototype.bind(). For now, just make that an empty literal that works with an obj parameter:

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}

Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
  };

Now the this keyword will refer to the function that inherits the bind() method. We'll save it to a variable named that so we can use that to build the new function. Traditionally, when you want to save this, you do so to a variable named that. But you don't have to. Like Haskell Curry, you could name the variable after yourself if you wanted.

if (Object.defineProperties === undefined) {
Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
    var that = this;
  };

By way of the comma operator, define another variable named ossify. Then borrow the slice() method from an empty array literal with the call() method, which slice() inherits from Function.prototype as any other function would. Then we'll invoke slice() in order to save any unnamed arguments passed to bind() as an array. So, ossify does not contain obj, just any additional arguments.

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
var that = this,
      ossify = [].slice.call(arguments, 1);
  };

By way of the comma operator, we'll define a third variable with the var statement. That one will be named fn and will contain the function value created by currying that and ossify.

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
    var that = this,
      ossify = [].slice.call(arguments, 1),
      fn = function () {
        return that.apply(this instanceof that ? this : obj,
ossify.concat([].slice.call(arguments, 0)));
      };
  };

Now in the event that that contains a constructor function, we need to ensure fn has the same prototype chain. To do so, we'd assign to fn.prototype the return value of passing that.prototype to Object.create(). This ensures that objects created by that and fn inherit the same members. Finally, we want to return the function value in fn, which is a combination of the function value in that and the parameters in ossify.

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
    var that = this,
      ossify = [].slice.call(arguments, 1),
      fn = function () {
        return that.apply(this instanceof that ? this : obj, ossify.concat([].slice.call(arguments, 0)));
      };
      fn.prototype = Object.create(that.prototype);
      return fn;
  };

Now for the moment of truth. Let's try currying a constructor that churns Wild Maine Blueberry ice cream with its blueberries and lemon parameters. Insofar as we're presetting those parameters to their typical winter values—no fresh wild blueberries to be had now—we'll name the new constructor WinterWildMaineBlueberry. Then since WinterWildMaineBlueberry just takes a vanilla parameter—the blueberries and lemon parameters are preset—we'll just pass "Tahitian" to choose a mild vanilla bean, verifying our work with Figure 6-11:

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
Function.prototype.bind = Function.prototype.bind ||
  function (obj) {
    var that = this,
      ossify = [].slice.call(arguments, 1),
fn = function () {
        return that.apply(this instanceof that ? this : obj,
ossify.concat([].slice.call(arguments, 0)));
      };
      fn.prototype = Object.create(that.prototype);
      return fn;
  };

var WildMaineBlueberry = function(blueberries, lemon, vanilla) {
  this.blueberries = [2, "cup", blueberries ? blueberries : "fresh wild Maine blueberries"];
  this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
};
WildMaineBlueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var WinterWildMaineBlueberry = WildMaineBlueberry.bind(null, "Dole frozen wild blueberries",
"Eureka");
var iceCream = new WinterWildMaineBlueberry("Tahitian");
console.dir(iceCream);
WinterWildMaineBlueberry() is created by combining WildMaineBlueberry() and its blueberries and lemon parameters.

Figure 6-11. WinterWildMaineBlueberry() is created by combining WildMaineBlueberry() and its blueberries and lemon parameters.

Chaining Methods

If you save a function to an object, the function is referred to as a method. Moreover, within the body of the method, this refers to the object you saved the method to. So, when you invoke a function as a constructor, this refers to the object the constructor returns, but when you invoke a function as a method, this refers to the object containing the function. Finally, if you invoke a function traditionally as a global method, then this refers to the global object window. However, ECMAScript 5 changes the value of this to null from window to prevent your coming to grief if you forget to invoke a constructor with new.

So anyway, to illustrate the point let's use Object.create() to create an object named iceCream that inherits methods named _french(), _vanilla(), and _coffee() from another object named churn. Then if we invoke those methods on iceCream, this will refer to iceCream and populate the members of iceCream. Therefore, as Figure 6-12 displays, iceCream will contain heavyCream, halfHalf, sugar, yolks, vanilla, and coffee members, which we can display by calling _print():

if (Object.defineProperties === undefined) {
  Object.defineProperties = function (obj, descriptors) {
      for (descriptor in descriptors) {
        if (descriptors.hasOwnProperty(descriptor)) {
          obj[descriptor] = descriptors[descriptor].value;
        }
      }
  };
}
if (Object.create === undefined) {
  Object.create = function (parent, descriptors) {
    var Proxy = function () {},
      child;
    Proxy.prototype = parent;
    child = new Proxy();
    if (descriptors !== undefined) {
      Object.defineProperties(child, descriptors);
    }
    return child;
  };
}
var churn = {};
churn._french = function (heavyCream, halfHalf, sugar, yolks) {
  this.heavyCream = [1, "cup", heavyCream || "Organic Valley"],
  this.halfHalf = [1, "cup", halfHalf || "Organic Valley"],
  this.sugar = [sugar || 5/8, "cup"],
  this.yolks = [yolks || 6]
};
churn._vanilla = function (vanilla) {
  this.vanilla = [1, "bean", vanilla || "Madagascar Bourbon"];
};
churn._coffee = function (coffee) {
  this.coffee = [1/4, "cup, coarsely ground", coffee || "Starbucks Espresso"];
};
churn._print = function () {
  var copy = {};
  for (var m in this) {
    this.hasOwnProperty(m) && (copy[m] = this[m]);
  }
  console.dir(copy);
};
var iceCream = Object.create(churn);
iceCream._french();
iceCream._vanilla();
iceCream._coffee();
iceCream._print();
this refers to the containing object for functions invoked as methods.

Figure 6-12. this refers to the containing object for functions invoked as methods.

That all worked fine, but there is a more elegant way to achieve what we just did, by chaining method calls together.

iceCream._french()._vanilla()._coffee()._print();

Let's see how to enable this technique.

Right now, our methods return undefined, but to chain a method to another one, we have to return this instead. As Figure 6-13 displays, doing so works just as well as, but more elegantly than, invoking the methods separately. Note that chaining methods is also referred to as cascades. Note too that chaining is very common in DOM and JavaScript libraries.

var clone = typeof Object.create === "function" ?
  Object.create :
  function (donor) {
    var Proxy = function () {};
    Proxy.prototype = donor;
return new Proxy();
  };
var churn = {};
churn._vanilla = function (vanilla) {
  this.vanilla = [1, "bean", vanilla || "Madagascar Bourbon"];
  return this;
};
churn._coffee = function (coffee) {
  this.coffee = [1/4, "cup, coarsely ground", coffee || "Starbucks Espresso"];
  return this;
};
churn._french = function (heavyCream, halfHalf, sugar, yolks) {
  this.heavyCream = [1, "cup", heavyCream || "Organic Valley"],
  this.halfHalf = [1, "cup", halfHalf || "Organic Valley"],
  this.sugar = [sugar || 5/8, "cup"],
  this.yolks = [yolks || 6]
  return this;
};
churn._coffee = function (coffee) {
  this.coffee = [1/4, "cup, coarsely ground", coffee || "Starbucks Espresso"];
  return this;
};
churn._print = function () {
  var copy = {};
  for (var m in this) {
    this.hasOwnProperty(m) && (copy[m] = this[m]);
  }
  console.dir(copy);
};
var iceCream = clone(churn);
iceCream._french()._vanilla()._coffee()._print();
Chaining method invocations

Figure 6-13. Chaining method invocations

Closure and Returning Functions

Vanilla is among the most powerful flavor enhancers, which is to say it enhances our ability to taste chocolate, coffee, fruit, nuts, and other foods. Moreover, vanilla elevates our perception of sweetness. For this reason, most ice-cream flavors are embellishments of vanilla ice cream.

So, while you could make chocolate ice cream from a sweet cream base, adding a vanilla bean boosts the chocolate flavor of the cocoa. To further do so, we could coarsely chop a cup, roughly 4 ounces, of bittersweet chocolate—I'd recommend Callebaut, Ghirardelli, or Lindt—and melt that into the custard.

Note that chocolate is made by crushing cacao beans and pressing them into an unsweetened paste, referred to as chocolate liqueur, which is comprised of cocoa and cocoa butter. So cocoa is made by removing the cocoa butter and grinding the now fat-free cacao paste into a powder. To remove some acidity and enhance the chocolate flavor, cocoa may be Dutched with alkali.

On the other hand, bittersweet chocolate is made by adding additional cocoa butter to pure chocolate liqueur and sweetening it with sugar. Fine bittersweet chocolate by Callebaut, Lindt, Ghirardelli, and others contain 60 to 70 percent pure cacao paste, while inexpensive bittersweet chocolate typically contains just 15 percent cacao, the FDA minimum. So paying an extra dollar or two for four or five times as much cacao is worth it if you love chocolate ice cream.

Let's get to the JavaScript to see what we can do with all this information. First, some background: any variable that you declare within the curly braces wrapping the block of an if, for, while, or other compound statement is visible outside the block, as you have seen throughout this book. Put another way, curly braces do not provide a way to create private variables.

In contrast, functions have function scope, which means that any variables or functions declared within the curly braces wrapping a function block normally are not visible outside of the block. Let's find out why.

Whenever you define a function, JavaScript saves the scope chain as part of the new function object; that is to say, the scope chain is the sequence of objects, beginning with the function's own call object and ending with the global window object. This means this part of the scope chain is set in stone before the function ever runs. However, any variables, functions, or arguments contained by the call and global objects comprising that scope chain are live.

Then, when you invoke a function, JavaScript adds any variables defined locally within the function, named parameters, the arguments object, and this to the scope chain. So these variable objects will differ every time you invoke the function. JavaScript looks in this complete set of items in the scope chain when it is looking up the value of a variable. In other words, when you invoke a function that uses a variable, JavaScript can only use the variable if it is declared in the scope chain.

Normally, after an invoked function returns, everything added to the scope chain when you invoked the function is destroyed. However, if you create a closure, the objects contained in function scope are not destroyed. Therefore, you may query the named parameters and locally defined variables for that invocation even after it has ended. Let's look at closures now.

Insofar as closure is a wildly popular technique, do yourself a favor and go ten toes in while we explore those now. Say we want to save some default values for bittersweet chocolate, cocoa, and vanilla to a closure our ChocolateChocolate() constructor can query. One way would be to define those as local variables for a self-invoking function and then have its return value be the ChocolateChocolate() constructor.

So, in the following sample, chocolateChocolate is churned with Callebaut cocoa and Madagascar Bourbon vanilla due to closure, as Figure 6-14 displays, along with the function JavaScript assigns to ChocolateChocolate.

var ChocolateChocolate = function () {
  var _bittersweet = "Ghirardelli",
    _cocoa = "Callebaut",
    _vanilla = "Madagascar Bourbon";
  return function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
}();
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
Querying local variables saved to a closure

Figure 6-14. Querying local variables saved to a closure

Another way to save default values for bittersweet chocolate, cocoa, and vanilla to a closure would be to define _bittersweet, _cocoa, and _vanilla parameters for the self-invoking function that returns the ChocolateChocolate() constructor. Then pass their values to the self-invoking function.

So, as Figure 6-15 displays, saving our default values as named parameters for a closure works just as well as saving those as local variables for the closure. Note that the definition of ChocolateChocolate() is the same as in the previous sample. The reason why we did not have to change the definition of ChocolateChocolate() is that there's no difference between named parameters and local variables on an activation object. That is to say, named parameters become local variables. The only difference between the two is the way you assign a value to them.

var ChocolateChocolate = function (_bittersweet, _cocoa, _vanilla) {
  return function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
}("Ghirardelli", "Callebaut", "Madagascar Bourbon");
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
Querying named parameters saved to a closure

Figure 6-15. Querying named parameters saved to a closure

A third way to save default values for bittersweet chocolate, cocoa, and vanilla to a closure would be to first declare the variable ChocolateChocolate and then assign a function to it from within the self-invoking function. That is to say, export a locally defined function to a global variable as in the following sample and Figure 6-16. Note that, as in the previous two samples, we did not have to change definition for the ChocolateChocolate() constructor. It's just the way we create a closure for it to query that differs. Note too that we wrap the self-invoking function within parentheses. Those are mandatory inasmuch as we want JavaScript to interpret the function as a function expression rather than a function declaration. That is to say, to prevent a syntax error.

var ChocolateChocolate = null;
(function (_bittersweet, _cocoa, _vanilla) {
  ChocolateChocolate = function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
}("Ghirardelli", "Callebaut", "Madagascar Bourbon"));
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
Exporting the constructor function from a self-invoking function expression

Figure 6-16. Exporting the constructor function from a self-invoking function expression

The previous sample could be reworked to use locally defined _bittersweet, _cocoa, and _vanilla variables rather than _bittersweet, _cocoa, and _vanilla named parameters. Try it, verifying your work with Figure 6-17.

var ChocolateChocolate = null;
(function () {
  var _bittersweet = "Ghirardelli",
    _cocoa = "Callebaut",
    _vanilla = "Madagascar Bourbon";
  ChocolateChocolate = function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
}());
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
Reworking the previous sample to use locally defined _bittersweet, _cocoa, and _vanilla variables rather than _bittersweet, _cocoa, and _vanilla named parameters

Figure 6-17. Reworking the previous sample to use locally defined _bittersweet, _cocoa, and _vanilla variables rather than _bittersweet, _cocoa, and _vanilla named parameters

Finally, note that, rather than wrapping the self-invocation in parentheses, you will often see the function expression wrapped in parentheses, which are then followed by the () operator as in the following sample. Try it, verifying your work with Figure 6-18:

var ChocolateChocolate = null;
(function (_bittersweet, _cocoa, _vanilla) {
  ChocolateChocolate = function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
})("Ghirardelli", "Callebaut", "Madagascar Bourbon");
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
ChocolateChocolate.toString();
Moving the () operator outside the parentheses works fine, too.

Figure 6-18. Moving the () operator outside the parentheses works fine, too.

Passing a Configuration Object

Often, if you have a number of optional parameters, such as in our closure samples, you will want to pass a configuration object rather than separate parameters. Doing so prevents your having to pass "" or some other falsy value parameters prior to the one you want to explicitly pass as in the following sample:

var ChocolateChocolate = function () {
  var _bittersweet = "Ghirardelli",
    _cocoa = "Callebaut",
    _vanilla = "Madagascar Bourbon";
  return function (bittersweet, cocoa, vanilla) {
    this.bittersweet = [1, "cup", bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", cocoa || _cocoa];
    this.vanilla = [1, "bean", vanilla || _vanilla];
  };
}();
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("", "", "Tahitian");
console.dir(chocolateChocolate);

So here's how we can remedy that bugaboo by defining one parameter named pref. As Figure 6-19 displays, this prevents our having to pass empty strings for bittersweet and cocoa in order to pass "Tahitian" for vanilla.

var ChocolateChocolate = function () {
  var _bittersweet = "Ghirardelli",
    _cocoa = "Callebaut",
    _vanilla = "Madagascar Bourbon";
  return function (pref) {
    pref || (pref = {});
    this.bittersweet = [1, "cup", pref.bittersweet || _bittersweet];
    this.cocoa = [3, "tbs", pref.cocoa || _cocoa];
    this.vanilla = [1, "bean", pref.vanilla || _vanilla];
  };
}();
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate({vanilla: "Tahitian"});
console.dir(chocolateChocolate);
Defining a configuration object in place of several defaut parameters

Figure 6-19. Defining a configuration object in place of several defaut parameters

Callback Functions

Insofar as functions are values, you can pass a function as a parameter to another function, which can then invoke it. A function passed and invoked this way is referred to as a callback function. Event listener functions, which we'll explore in Chapter 9, are the most common type of callback function. But they're not the only way to implement this pattern.

For example, we could rework our clone function from earlier in the chapter so that we can pass it either an object or a callback constructor function. So let's do so now, and then test it both ways by passing it a constructor named SweetCream as well as by passing it SweetCream.prototype. As Figure 6-20 displays, the end result is the same.

var clone = typeof Object.create === "function" ?
  function (donor) {
    return typeof donor !== "function" ?
      Object.create(donor) :
      Object.create(new donor());
  } :
  function (donor) {
    var Proxy = function () {};
    Proxy.prototype = typeof donor !== "function" ?
      donor :
      new donor();
    return new Proxy();
  };

var SweetCream = function () {};
SweetCream.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var sweetCream = clone(SweetCream);
console.dir(sweetCream);
var sweetCream2 = clone(SweetCream.prototype);
console.dir(sweetCream2);
Passing a SweetCream() callback function to clone()

Figure 6-20. Passing a SweetCream() callback function to clone()

Memoization

In the event that you have a function that does memory-intensive or identically repetitive work, you might want to cache its return values to a closure. Doing so is referred to as memoization. To illustrate this technique, let's memoize our ChocolateChocolate() constructor. To do so, we'll save a local variable named memo to a closure. Then, every time ChocolateChocolate() is invoked, we'll add a member to memo containing the returned object. Those members will be named with a string created by gluing the parameter values together with underscores. Just for the purposes of running this in Firebug, we'll pass the memo object to console.dir so that we can view its members following each invocation of ChocolateChocolate().

So, as Figure 6-21 displays, invoking ChocolateChocolate() two times in a row with the same parameters, returns the object from memo the second time. To verify that, we can compare the return values with ===. Remember from Chapter 3 that === returns true for two objects only if their heap memory locations are the same. That is to say, to ===, no two quarts of double chocolate ice cream are the same—a quart of double chocolate ice cream can only be equal to itself.

var ChocolateChocolate = function () {
  var memo = {};
  return function (bittersweet, cocoa, vanilla) {
    var m = bittersweet + "_" + cocoa + "_" + vanilla;
    if (typeof memo[m] === "object") {
      return memo[m];
    }
    this.bittersweet = [1, "cup", bittersweet || "Callebaut"];
    this.cocoa = [3, "tbs", cocoa || "Callebaut"];
    this.vanilla = [1, "bean", vanilla || "Madagascar Bourbon"];
    memo[m] = this;
    console.dir(memo);
  };
}();
ChocolateChocolate.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var chocolateChocolate = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
var chocolateChocolate2 = new ChocolateChocolate("Lindt");
console.dir(chocolateChocolate);
chocolateChocolate === chocolateChocolate2;
Memoizing ChocolateChocolate()

Figure 6-21. Memoizing ChocolateChocolate()

Global Abatement with Modules

Self-invoking function expressions, which we used earlier in the closure samples, can be used to eliminate global variables, referred to as global abatement, by way of modules. Eliminating global variables is often a good idea to avoid name clashes with local variables. In other words, a global variable could easily have the same name as a local variable, which is not a good idea. The script we'll hand-code at the end of this book will leave no global footprint due to the module pattern, which is quite simple.

Just put your script within the body of a self-invoking function expression. As discussed earlier, the () operator may go inside or outside the parentheses wrapping the function expression. So one of the following two patterns will do:

(function () {
// paste script here
}());

(function () {
// paste script here
})();

Using the first way, let's paste in our callback sample from earlier in the chapter. As Figure 6-22 and 6-23 display, we can invoke clone() from within the module but not outside of it for the reason that clone() and SweetCream() are not globally defined. So JavaScript returns a reference error in Figure 6-23.

(function () {
var clone = typeof Object.create === "function" ?
  function (donor) {
    return typeof donor !== "function" ?
      Object.create(donor) :
      Object.create(new donor());
  } :
  function (donor) {
    var Proxy = function () {};
    Proxy.prototype = typeof donor !== "function" ?
      donor :
      new donor();
    return new Proxy();
  };

var SweetCream = function () {};
SweetCream.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
console.dir(clone(SweetCream));
}());
Calling clone() within the module works

Figure 6-22. Calling clone() within the module works

(function () {
var clone = typeof Object.create === "function" ?
  function (donor) {
    return typeof donor !== "function" ?
      Object.create(donor) :
      Object.create(new donor());
  } :
  function (donor) {
    var Proxy = function () {};
    Proxy.prototype = typeof donor !== "function" ?
      donor :
      new donor();
    return new Proxy();
  };

var SweetCream = function () {};
SweetCream.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
}());
console.dir(clone(SweetCream));
clone() and SweetCream() are not globally defined.

Figure 6-23. clone() and SweetCream() are not globally defined.

Arrays

Not only did Ben & Jerry's churn Wild Maine Blueberry from 1990 to 1992, but my Pittsburgh Pirates made it to the NL championship series during those years, too. But in 1993, Wild Maine Blueberry was laid to rest, and Barry Bonds left for San Francisco, where he would set MLB marks for most MVP awards (7), homeruns in a season (73), and homeruns in a career (762). My Pirates then embarked on a streak of losing seasons, 18 as of this writing, unrivaled in the history of North American pro sports (MLB, NFL, NBA, NHL).

So anyway, let's explore some predefined array methods with an array of arrays representing the Pirates' unenviable streak of futility, which we'll assign to a variable named pirates. Note that these predefined methods are saved to Array.prototype. So every array inherits those methods by way of the prototype chain in the same way that our ice-cream objects inherited members like halfHalf and yolks from the prototype chain.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];

Note

pirates is a valid JSON array. We'll explore JSON, the popular Ajax data exchange format in Chapter 10.

Plucking Elements from an Array

Alright, to pluck the first element in pirates we'd invoke its shift() method. So, if we pass the return value of shift() to console.dir(), JavaScript will print the 2010 season for us in Firebug as Figure 6-24 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
[2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
console.dir(pirates.shift());
shift() removes and returns the first element in an array.

Figure 6-24. shift() removes and returns the first element in an array.

Note that shift() modifies pirates, too, by shifting the contents down one place in the array every time it is invoked. This means that element 0 is removed, element 1 becomes element 0, and so on. So, after invoking shift() three times in a row, pirates has just 15 elements rather than 18 as Figure 6-25 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
[1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.shift(),
  pirates.shift(),
  pirates.shift(),
  console.dir(pirates);
pirates has 15 elements rather than 18 after invoking its shift() method three times.

Figure 6-25. pirates has 15 elements rather than 18 after invoking its shift() method three times.

Naturally, JavaScript provides a method to pluck the last element from an array, which is named pop(). So, invoking pop() on pirates three times in a row removes and returns the 1993, 1994, and 1995 seasons, as Figure 6-26 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
[1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
console.dir(pirates.pop()),
  console.dir(pirates.pop()),
  console.dir(pirates.pop());
pop() removes and returns the last element in an array.

Figure 6-26. pop() removes and returns the last element in an array.

Note that, like shift(), pop() modifies the array it is invoked upon. So, after invoking pop() three times on pirates, there are just 15 elements left as Figure 6-27 displays:

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.pop(),
  pirates.pop(),
  pirates.pop(),
  console.dir(pirates);
pirates has 15 elements rather than 18 after invoking its pop() method three times.

Figure 6-27. pirates has 15 elements rather than 18 after invoking its pop() method three times.

Adding Elements to an Array

To do the inverse of shift(), which is to say add an element to the beginning of an array, call an array's unshift() method. The Pirates hired a new manager, Clint Hurdle, for 2011. So I'm hopeful they'll win say 15 more games than in 2010. Let's add that prediction of 72 wins and 90 losses to pirates by invoking its unshift() method. Then pass pirates to console.dir() and JavaScript will print the modified array in Firebug as Figure 6-28 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
[1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.unshift([2011, 72, 90]),
console.dir(pirates);
unshift() adds an element to the beginning of an array.

Figure 6-28. unshift() adds an element to the beginning of an array.

Similarly, pop() has an inverse, too. But it's not named unpop(), but push(). So let's add the 1992, 1991, and 1990 glory years to pirates by calling push() three times in a row. Then pass pirates to console.dir(), verifying our work with Figure 6-29.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
[1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.push([1992, 96, 66]),
  pirates.push([1991, 98, 64]),
  pirates.push([1990, 95, 67]);
console.dir(pirates);
push() adds an element to the end of an array.

Figure 6-29. push() adds an element to the end of an array.

Gluing Two Arrays Together

Alrighty, to glue two arrays together, you would invoke concat() on the first array, passing the second array as a parameter. So, if we had the past nine seasons in an array named pirates1 and the nine seasons prior to those in pirates2, we'd create the losing streak array by calling concat() on pirates1 while passing in pirates2. Try it and then query the first and last elements in pirates, verifying your work with Figure 6-30.

var pirates1 = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
[2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89]];
var pirates2 = [[2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var pirates = pirates1.concat(pirates2);
console.dir(pirates[0]);
console.dir(pirates[pirates.length - 1]);
Gluing arrays together with concat()

Figure 6-30. Gluing arrays together with concat()

Note that creating pirates by gluing pirates1 to pirates2 does not modify pirates1 or pirates2. That is to say, the last element in pirates1 is still the 2002 season and the first element in pirates2 is still the 2001 season as Figure 6-31 displays. So, unlike methods like pop(), push(), shift(), and unshift(), a concat() does not modify the array it's invoked upon. In geeky terms, we would say that pop() is a mutator method while concat() is an accessor method.

var pirates1 = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
[2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89]];
var pirates2 = [[2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var pirates = pirates1.concat(pirates2);
console.dir(pirates1[pirates.length - 1]);
console.dir(pirates2[0]);
concat() does not modify the array its invoked upon nor the one passed to it as an argument.

Figure 6-31. concat() does not modify the array its invoked upon nor the one passed to it as an argument.

Reversing the Elements in an Array

Now say we wanted to reverse the order of seasons in pirates from 2010-1993 to 1993-2010. To do so, we'd simply call the aptly named method reverse() on pirates. So, as Figure 6-32 displays, querying the first element in pirates before and after calling reverse() returns the 2010 and 1993 season, respectively:

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
[2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
console.dir(pirates[0]);
pirates.reverse();
console.dir(pirates[0]);
Reversing the order of elements in an array by invoking its reverse() method

Figure 6-32. Reversing the order of elements in an array by invoking its reverse() method

Sorting the Elements in an Array

Though reversing the order of elements in pirates is interesting, it would be more helpful if we could reorder those by wins or losses. To do so, we'd invoke sort() on pirates and pass a function value to do the reordering work. JavaScript will pass that function two elements from pirates—yup, happens by magic—and the function will then return −1 if the first element ought to come before the second element, 0 if there is a tie, and 1 if the second element ought to come before the first element. Though it's typical for the function to return −1 and 1, any negative or positive integer will do. The only return value set in stone is 0, which conveys a tie.

So alrighty then, let's save a function to a variable named sortByLosses that will sort pirates by losses. Begin by defining parameters named e1 and e2 for JavaScript to pass elements in pirates to sortByLosses() with.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByLosses = function (e1, e2) {
};

Then write an if condition to handle the case where the Pirates lost the same number of games in e1 and e2. If so, we want to sort the seasons by year, putting the most recent season first. So we'll have sortByLosses() return −1 if the year in e1[0] is more recent than e2[0], and 1 if e1[0] is more recent than e2[0]:

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByLosses = function (e1, e2) {
  if (e1[2] === e2[2]) {
    return e1[0] > e2[0] ? −1 : 1;
  }
};

Now append an else clause that has sortByLosses() return −1 if the Pirates lost more games in e1 than e2, and 1 if the Pirates lost more games in e2 than e1:

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByLosses = function (e1, e2) {
  if (e1[2] === e2[2]) {
    return e1[0] > e2[0] ? −1 : 1;
  } else {
    return e1[2] > e2[2] ? −1 : 1;
  }
};

OK, time to see if sortByLosses() does what we want it to. So invoke sort() on pirates and pass the sortByLosses identifier—don't invoke sortByLosses with the () operator or you'll come to grief. sort() will then reorder and return pirates. So if we invoke console.dir() on the return value of sort(), JavaScript will print the reordered array in Firebug as Figure 6-33 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByLosses = function (e1, e2) {
  if (e1[2] === e2[2]) {
    return e1[0] > e2[0] ? −1 : 1;
} else {
    return e1[2] > e2[2] ? −1 : 1;
  }
};
console.dir(pirates.sort(sortByLosses));
Sorting elements in the pirates array by losses and then by year

Figure 6-33. Sorting elements in the pirates array by losses and then by year

Forgive me for being negative and sorting by losses. In Pittsburgh, between the end of the Penguins season and beginning of the Steelers season, guessing how many games the Pirates will lose and which star players they'll trade away for prospects is pretty much all there is to do sports-wise. So let's be optimistic and sort pirates by wins instead. Doing so is trivial, just rename the function sortByWins and change the indexes in the boolean expression and else clause from 2 to 1 like so.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
[1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByWins = function (e1, e2) {
  if (e1[1] === e2[1]) {
    return e1[0] > e2[0] ? −1 : 1;
  } else {
    return e1[1] > e2[1] ? −1 : 1;
  }
};

Then invoke sort() on pirates, but pass the identifier sortByWins instead of sortByLosses. As before, pass the return value of sort() to console.dir() and JavaScript will print the reordered pirates array in Firebug as in Figure 6-34.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
var sortByWins = function (e1, e2) {
  if (e1[1] === e2[1]) {
    return e1[0] > e2[0] ? −1 : 1;
  } else {
    return e1[1] > e2[1] ? −1 : 1;
  }
};
console.dir(pirates.sort(sortByWins));
Sorting elements in the pirates array by wins and then by year

Figure 6-34. Sorting elements in the pirates array by wins and then by year

Creating a String from an Array

Often, you will want to create a string from an array. To do so, you would invoke the array's join() method, which converts the elements in the array to strings and then sequentially glues them together. join() takes an optional parameter, which is a separator to glue the elements together with. Note that, if you omit the separator, then JavaScript will use "," by default.

So say we'd like to create a comma-separated list from pirates, with each entry on a single line, we would pass "/n" as the separator. JavaScript will then invoke toString() on each element in pirates and join those strings together with "/n".

Note

This format is called a comma-separated value (CSV) string. It can be imported into spreadsheets or processed by other applications, because it is a standard format.

So, as Figure 6-35 displays, join() returns the Pirates' 18 years of futility as a CSV string:

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
[2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.join("
");
Gluing elements together with join()

Figure 6-35. Gluing elements together with join()

Taking a Slice of an Array

Sometimes you will want to copy two or more elements from an array. To do so, you would invoke its slice() method, passing two parameters.

  • First, the index of the first element to copy.

  • Second, the number of elements to copy.

Note

slice() makes a shallow copy of an array. That is to say, elements containing object, array, and function values are copied by reference, not duplicated.

So, say we'd like to know how the Pirates did during the past five years, we'd invoke slice() on pirates and pass 0 and 5. As Figure 6-36 displays, slice() returns copies of those five seasons, but does not remove them from pirates.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
console.dir(pirates.slice(0, 5));
pirates.length;
// 18
Shallow copying elements from an array with slice()

Figure 6-36. Shallow copying elements from an array with slice()

Converting a Read-only Array-like Object to an Array

slice() is commonly borrowed by read-only array-like objects such as arguments in order to copy their elements to a real, read-write array. To very simply illustrate this technique, let's create a function named argumentsToArray() that will copy elements from its arguments object to an array and then return that array. Then if we pass 18 parameters to argumentsToArray(), one for each losing season in the Pirates' streak of futility, those will first get saved to its arguments object. Then we'll have arguments borrow slice(), which will return a real array containing 18 elements, one for each losing season. Therefore, if we save the return value of argumentsToArray() to a variable named pirates, it will contain the very same array we've been mucking around with in this chapter as Figure 6-37 displays.

var argumentsToArray = function () {
  return Array.prototype.slice.call(arguments);
};
var pirates = argumentsToArray([2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]);
console.dir(pirates);
Shallow copying members from arguments to an array by borrowing slice().

Figure 6-37. Shallow copying members from arguments to an array by borrowing slice().

Or more succinctly, we can borrow slice() from an empty array literal rather than from Array.prototype. As Figure 6-38 displays, this works just as well and saves some keystrokes. Consequently, borrowing slice() this way is very common.

var argumentsToArray = function () {
  return [].slice.call(arguments);
}
var pirates = argumentsToArray([2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]);
console.dir(pirates);
A more succinct way of creating an array from the members in arguments

Figure 6-38. A more succinct way of creating an array from the members in arguments

So argumentsToArray() invoked the array method slice() on the arguments object, which is like the arg object below, but read-only. Then, insofar as we did not pass an index to begin the slice at the number of elements to copy, slice() copies every element in arguments and returns those in an array. This works for the reason that any member in an object named with a non-negative integer string is really an element. So while an object like arguments does not inherit any array methods, it can contain elements. So slice() copies the members named "0" through "17", but not the one named "length" or "callee". Pretty cool, don't you think?

var arg = {
  "0": [2010, 57, 105],
  "1": [2009, 62, 99],
  "2": [2008, 67, 95],
  "3": [2007, 68, 94],
  "4": [2006, 67, 95],
  "5": [2005, 67, 95],
  "6": [2004, 72, 89],
  "7": [2003, 75, 87],
  "8": [2002, 72, 89],
  "9": [2001, 62, 100],
  "10": [2000, 69, 93],
  "11": [1999, 78, 83],
  "12": [1998, 69, 93],
  "13": [1997, 79, 83],
  "14": [1996, 73, 89],
  "15": [1995, 58, 86],
"16": [1994, 53, 61],
  "17": [1993, 75, 87],
  "length": 18,
  "callee": argumentsToArray
};

Inserting or Deleting Elements from an Array

Now then, say the elements for the 2000-2002 seasons are invalid inasmuch as the wins and losses are reversed, and 2002 is in there twice. To delete those four invalid elements and insert new ones in their place, we can invoke splice() on pirates.

  • The first parameter is the index of the first element to delete, so 8 for the 2002 season.

  • The second parameter is the number of elements to delete, which would be 4.

  • From there, any additional parameters are elements to splice into the array. Note that it's OK to add more or fewer elements than you deleted; JavaScript will keep the array indexes sequential behind the scenes.

So if we add three elements in place of the four invalid ones, the indexes in pirates remain sequential, ordered 0 to 17 as Figure 6-39 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 89, 72],
  [2002, 89, 72],
  [2001, 100, 62],
  [2000, 93, 69],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.splice(8, 4, [2002, 72, 89], [2001, 62, 100], [2000, 69, 93]);
console.dir(pirates);
Deleting and inserting elements into an array with splice()

Figure 6-39. Deleting and inserting elements into an array with splice()

Now, say the 2000-2002 seasons are simply missing. We can use splice() to insert those. That is to say, splice() does not require you to delete any elements. So if we pass 0 for the second parameter to splice(), JavaScript will insert the three new elements, and, as before, keep the indexes sequential behind the scenes, as Figure 6-40 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87]];
pirates.splice(8, 0, [2002, 72, 89], [2001, 62, 100], [2000, 69, 93]);
console.dir(pirates);
There's no need to delete elements prior to inserting new ones with splice().

Figure 6-40. There's no need to delete elements prior to inserting new ones with splice().

Conversely, splice() can be used just to delete elements. That is to say, we're not required to insert new elements in place of the ones we delete. So say pirates contains elements for the 1990-92 seasons and we want to delete those so that only the seasons comprising the 18-year losing streak remain. To do so, we'd simply pass 18 and 3 to splice(). JavaScript will then clip those three winning seasons from pirates, as Figure 6-41 displays.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
  [1993, 75, 87],
  [1992, 96, 66],
[1991, 98, 64],
  [1990, 95, 67]];
pirates.splice(18, 3);
console.dir(pirates);
There's no need to replace deleted elements with new ones when invoking splice().

Figure 6-41. There's no need to replace deleted elements with new ones when invoking splice().

Even more succinctly, we could accomplish the same thing by simply passing 18. JavaScript will then delete any elements from that index all the way to the end of the array. So, as Figure 6-42 displays, passing 18 modifies pirates, just as passing 18 and 3 did.

var pirates = [[2010, 57, 105],
  [2009, 62, 99],
  [2008, 67, 95],
  [2007, 68, 94],
  [2006, 67, 95],
  [2005, 67, 95],
  [2004, 72, 89],
  [2003, 75, 87],
  [2002, 72, 89],
  [2001, 62, 100],
  [2000, 69, 93],
  [1999, 78, 83],
  [1998, 69, 93],
  [1997, 79, 83],
  [1996, 73, 89],
  [1995, 58, 86],
  [1994, 53, 61],
[1993, 75, 87],
  [1992, 96, 66],
  [1991, 98, 64],
  [1990, 95, 67]];
pirates.splice(18);
console.dir(pirates);
If we omit the second parameter, JavaScript deletes elements all the way to the end of the array.

Figure 6-42. If we omit the second parameter, JavaScript deletes elements all the way to the end of the array.

Summary

In this chapter, we explored functions and arrays, which are subtypes of the object value type. JavaScript functions are first-class objects, which is to say they are values that can be expressed with function literal notation. Functions also provide local scope. The diverse array of function techniques we explored in this chapter derive from those two distinctive features.

I expanded on Chapter 5's discussion of inheritance by showing how to pass on functions to child objects. This technique allows you to build scripts for all sorts of purposes, including conditional advance loading, lazy loading, recursion, function borrowing, and currying. All of these cunning tricks have their place in a JavaScript application and make life a lot easier when you have to make a snappy and useful application. You've learned a lot of advanced programming techniques in this chapter, maybe without realizing it, which will stand you in good stead for the chapters to come.

We also explored methods that all arrays inherit by way of the prototype chain from Array.prototype. Some of those like pop() and splice() modify, which is to say mutate, the array they are invoked upon. Others like slice() or join() query an array in order to return a new value, but do not modify the array. Those inherited methods, along with the length member, differentiate the array subtype from the object type.

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

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