Chapter 5. Member Inheritance

In this chapter, I'll cover a feature of JavaScript called inheritance, which is a very useful and powerful tool to acquire. It allows us to write much more ordered and efficient code, because it is a great way to organize your code into useful little lumps. I will cover the following four ways to approach inheritance, each of which has its own place in your programs:

  • Classical

  • Prototypal

  • Deep copy

  • Mixin

As you may have guessed from the chapter title, this chapter covers only member inheritance; Chapter 6 covers how to use inheritance with object functions.

To get to grips with inheritance, you first have to get to know objects a little bit better, so I will start by covering object constructors to see how objects are defined and created. You've already seen a few constructors in previous chapters (anything preceded by the new keyword is a constructor), but in this chapter, you will see how to define constructors for yourself.

Creating Objects with a Constructor

Ben & Jerry's Wild Maine Blueberry rests peacefully in a small graveyard on a grassy knoll encircled by a white picket fence in Waterbury, Vermont. The epitaph on its humble tombstone reads as follows:

Wild Maine Blueberry Wild Maine Blueberry From the land of the puffin, Now when we crave you, We turn to the muffin. 1990–92

Blueberries are my favorite fruit—my mom nicknamed me Blueberry as a child for that reason and because it rhymes with Terry—so I took it pretty hard when Wild Maine Blueberry died. I may have even stopped talking for a while.

But time heals all wounds. Or in this case, an ice cream maker did. So, now I churn my own Wild Maine Blueberry from the following recipe. Note that I puree one of the two cups of wild Maine blueberries, the pulp for which is removed with the vanilla pod and seeds by straining the French-style custard through a fine mesh sieve.

  • 1 cup, Organic Valley heavy whipping cream

  • 1 cup, Organic Valley half & half

  • 5/8 cup, sugar

  • 6 egg yolks

  • 2 cups, wild Maine blueberries

  • 1 Madagascar Bourbon vanilla bean

  • 2 tsp. fresh lemon juice

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 do this. So anyway, we can represent this recipe with the following wildMaineBlueberry object:

var wildMaineBlueberry = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6],
  blueberries: [2, "cup", "fresh wild Maine blueberries"],
  vanilla: [1, "bean", "Madagascar Bourbon"],
  freshLemonJuice: [2, "tsp"]
};

However, other than a few short months, fresh wild Maine blueberries are tough to find. So for most of the year, I'd have to set wildMaineBlueberry.blueberries to either [2, "cup", "Dole frozen wild blueberries"] or [2, "cup", "Wyman frozen wild blueberries"]. Moreover, Madagascar Bourbon is my favorite type of vanilla bean. But if I'm out of those, I'll steep a Tahitian or Mexican one instead. The former is milder than Madagascar Bourbon while the latter is more intense.

So, what do you do? Modify wildMaineBlueberry.blueberries and wildMaineBlueberry.vanilla by hand whenever my preferred ingredients are not to be had? Well, we could, but that's so greenhorn. There's a better way: create a constructor to churn custom quarts of Wild Maine Blueberry for us. Here's how:

Constructors are functions invoked with the new operator. Unlike typical functions, constructors are named in upper camel case to indicate that new is required. Omitting new for a constructor adds members to the global window object, which would not be good.

So, anyway, when you invoke a constructor with new, JavaScript creates a private variable named this, which contains an empty object for you to add members to. Whereas functions implicitly return undefined, constructors implicitly return this. So you don't have to create a private this variable in the body of the constructor or explicitly write a return statement. JavaScript does that for you.

That's really nice. But even better, this inherits any members you add to the constructor's prototype object. With all of this in mind, we can create a constructor named WildMaineBlueberry(). Carved in stone members will go in WildMaineBlueberry.prototype, while the values we pass in parameters cleverly named blueberries and vanilla will be assigned to the blueberries and vanilla members of this. In other words, we set the prototype to contain all the ingredients that never change and use the constructor to customize the ingredients that do change, which are then combined with the unchanging set.

In the following constructor, you can see how we use ?: to set the default values of blueberries and vanilla, should we not supply a blueberries parameter or a vanilla parameter (in other words, the value of the blueberries parameter is returned by ?: if it is set; otherwise, the default string is returned):

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"]
};

Now let's have WildMaineBlueberry() churn us a quart of Ben & Jerry's Wild Maine Blueberry with the preferable default values for blueberries and vanilla. Then pass that to Firebug's console.dir() method, which as Figure 5-1 displays, prints an object's own and inherited members. Note that own members are those we add to this, so blueberries and vanilla:

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);
Firebug's console.dir() method prints an object's own and inherited members.

Figure 5-1. Firebug's console.dir() method prints an object's own and inherited members.

Now this time, let's churn a less preferable but still yummy quart with Dole frozen wild blueberries and a Tahitian bean, verifying our work with Figure 5-2:

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");
console.dir(wildMaineBlueberry);
Churning a quart with Dole frozen wild blueberries and a Tahitian bean

Figure 5-2. Churning a quart with Dole frozen wild blueberries and a Tahitian bean

Finally, let's be ding-dongs and forget to invoke WildMaineBlueberry() with new. As Figure 5-3 displays, WildMaineBlueberry() returns undefined, and there are now global variables named blueberries and vanilla. Great googly moogly!

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 = WildMaineBlueberry();
typeof wildMaineBlueberry;
// "undefined"
blueberries;
// [2, "cup", "fresh wild Maine blueberries"]
vanilla;
// [1, "bean", "Madagascar Bourbon"]
Don't forget to invoke constructors like WildMaineBlueberry() with new, or you'll be creating or overwriting global variables.

Figure 5-3. Don't forget to invoke constructors like WildMaineBlueberry() with new, or you'll be creating or overwriting global variables.

Now that you know how to create custom objects using a constructor, it's time to talk about inheritance. This will show you how to take advantage of constructors.

Classical Inheritance

Like Wild Maine Blueberry, many ice cream flavors begin with a vanilla base, which other ingredients are then added to. This is something we'd often like to do in JavaScript; in other words, if we have some object that has some desirable members, we can take that object and add other members to it to create another kind of object. The new object is based on the old one and has all the members of the old one, in addition to all the new ones we have added. This means we can place useful common members in one object and then base other, more specialized, objects on it, as well as make more specialized versions of useful objects in our code. The feature in JavaScript to do this is called inheritance, because the new object inherits the members of the old object.

Back to the ice cream to see inheritance in action: the very best vanilla ice cream is made by steeping a vanilla bean rather than adding vanilla extract. There are three types of vanilla beans. Madagascar Bourbon is the most common. I prefer those to the more floral Tahitian or intense Mexican beans.

So, let's create a VanillaBean() constructor that optionally takes a vanilla parameter, which would contain the bean type as a string. By way of the ?: operator, we'll default vanilla to "Madagascar Bourbon":

var VanillaBean = function(vanilla) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
}

Oftentimes I'll steep a stick of Saigon cinnamon with the vanilla bean. That works especially well for fruit flavors like peach or if you're serving the ice cream with pie. So, let's add an optional boolean parameter named cinnamon. By way of the && operator, VanillaBean() will add a cinnamon stick only if cinnamon is true. Note that insofar as && has higher precedence than =, we need to wrap the = expression in parentheses.

var VanillaBean = function(vanilla, cinnamon) {
this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};

Having defined VanillaBean(), we can now put add carved in stone members to VanillaBean.prototype:

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};

Having done so, let's create a VanillaBean() instance named vanilla, verifying our work with Figure 5-4. Note that vanilla has its own vanilla and cinnamon members but inherits other members from VanillaBean.prototype.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var vanilla = new VanillaBean("Tahitian", true);
console.dir(vanilla);
vanilla has its own vanilla and cinnamon members but inherits other members from VanillaBean.prototype.

Figure 5-4. vanilla has its own vanilla and cinnamon members but inherits other members from VanillaBean.prototype.

Now let's create a Coffee() constructor that will inherit from VanillaBean(). Unless otherwise specified in an optional coffee parameter, we'll steep coarsely ground Starbucks Espresso with a Madagascar Bourbon vanilla bean.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();

Note that Coffee.prototype contains just one member, vanilla, which contains [1, "bean", "Madagascar Bourbon"], because we didn't supply the optional cinnamon parameter to the Vanilla() constructor. So for Coffee(), the prototype chain would be as follows:

VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
Coffee.prototype = {
  vanilla: [1, "bean", "Madagascar Bourbon"],
};

Having done so, let's create a Coffee() instance named coffee, verifying our work with Figure 5-5. Note that coffee has its own coffee member but inherits other members from Coffee.prototype and VanillaBean.prototype.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var coffee = new Coffee();
console.dir(coffee);
coffee has its own coffee member but inherits other members from Coffee.prototype and VanillaBean.prototype.

Figure 5-5. coffee has its own coffee member but inherits other members from Coffee.prototype and VanillaBean.prototype.

Now let's create a Chocolate() constructor that also inherits members from VanillaBean(). Optional cocoa and bittersweet parameters will allow us to specify Dutch cocoa and bittersweet chocolate other than Callebaut, though Callebaut will be substituted in different quantities for each parameter if they are missing. We'll then set the prototype, as we did for Coffee(). However, we'll add a yolks member to override the one on VanillaBean.prototype, because chocolate ice cream takes fewer yolks than vanilla:

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();

var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];

Having done so, let's create a Chocolate() instance named chocolate, verifying our work with Figure 5-6. Note that chocolate has its own cocoa and bittersweet members and inherits other members from Chocolate.prototype and VanillaBean.prototype.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();

var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var chocolate = new Chocolate([1/4, "cup", "Bensdorp"]);
console.dir(chocolate);
chocolate has its own cocoa and bittersweet members and inherits other members from Chocolate.prototype and VanillaBean.prototype.

Figure 5-6. chocolate has its own cocoa and bittersweet members and inherits other members from Chocolate.prototype and VanillaBean.prototype.

Though it's not in my top 10, Ben & Jerry's Mint Chocolate Chunk is pretty good, so let's define a MintChocolateChunk() constructor and chain MintChocolateChunk.prototype to Chocolate.prototype by invoking Chocolate() with the new operator. Note that you would steep the mint leaves with the vanilla pod and seeds, later removing both by straining through a fine mesh sieve. Note too that you would add the Callebaut bittersweet chunks at the very end of the churning phase.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();

Now let's add a vanilla member to override Chocolate.prototype.vanilla. Insofar as we're chipping the Callebaut bittersweet chocolate, we need only 1 cup, rather than 1 1/2 cups. So, let's modify the first element in the MintChocolateChunk.prototype.bittersweet array. Finally, we don't want the cocoa, so pass that member to the delete operator, which we covered in Chapter 3.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];

var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;

Having done so, let's create a MintChocolateChunk() instance named mintChocolateChunk, verifying our work with Figure 5-7. Note that mintChocolateChunk has its own mint member and inherits other members from MintChocolateChunk.prototype, Chocolate.prototype, and VanillaBean.prototype.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];

var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;
var mintChocolateChunk = new MintChocolateChunk();
console.dir(mintChocolateChunk);
mintChocolateChunk has its own mint member and inherits other members from MintChocolateChunk.prototype, Chocolate.prototype, and VanillaBean.prototype.

Figure 5-7. mintChocolateChunk has its own mint member and inherits other members from MintChocolateChunk.prototype, Chocolate.prototype, and VanillaBean.prototype.

Determining Which Type or Types an Object Is an Instance Of

The prototype chain, such as those we saw earlier, determines which type or types an object such as mintChocolateChunk is an instance of, that is, what constructor or constructors an object inherits members from. To figure this out, you would use the aptly named instanceof operator, which we didn't cover in Chapter 3. Note that instanceof, like typeof, is in lowercase, not camel case.

Let's query JavaScript in regard to what types mintChocolateChunk is an instance of with instanceof. Note that the second operand to instanceof is the identifier for the constructor, so don't append the () operator. Note too that like every object, mintChocolateChunk is an instance of the Object() constructor, from which it inherits methods like valueOf() and toString(). So as Figure 5-8 displays, mintChocolateChunk is an instance of four types, which is to say it inherits members from four constructors.

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;
var mintChocolateChunk = new MintChocolateChunk();


mintChocolateChunk instanceof MintChocolateChunk;
// true
mintChocolateChunk instanceof Chocolate;
// true
mintChocolateChunk instanceof Coffee;
// false
mintChocolateChunk instanceof VanillaBean;
// true
mintChocolateChunk instanceof Object;
// true
mintChocolateChunk is an instance of four types, which is to say it inherits members from four constructors.

Figure 5-8. mintChocolateChunk is an instance of four types, which is to say it inherits members from four constructors.

Inherited Members Are Shared Not Copied

It's important to note inherited members are shared among instances. So if you recall from Chapter 3, the === will return true for a member that two instances share, even a member that is an object, array, or function. To illustrate the point, let's chain && comparisons in order to confirm that instances of VanillaBean, Coffee, Chocolate, and MintChocolateChunk share the heavyCream array, [1, "cup", "Organic Valley"], verifying our work with Figure 5-9:

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;

var vanilla = new VanillaBean("Tahitian", true);

var coffee = new Coffee();
var chocolate = new Chocolate([1/4, "cup", "Bensdorp"]);
var mintChocolateChunk = new MintChocolateChunk();

vanilla.heavyCream === coffee.heavyCream &&
  vanilla.heavyCream === chocolate.heavyCream &&
  vanilla.heavyCream === mintChocolateChunk.heavyCream &&
  mintChocolateChunk.heavyCream === coffee.heavyCream &&
  coffee.heavyCream === chocolate.heavyCream &&
  mintChocolateChunk.heavyCream === chocolate.heavyCream;
// true
Any instance of VanillaBean, Coffee, Chocolate, or MintChocolateChunk shares the heavyCream array, [1, "cup", "Organic Valley"].

Figure 5-9. Any instance of VanillaBean, Coffee, Chocolate, or MintChocolateChunk shares the heavyCream array, [1, "cup", "Organic Valley"].

What are the implications of sharing inherited members? Changing an inherited member immediately changes all instances, both old and new, that inherit it. If you're thinking this would make it easier to look after your code when something has to change, I owe you a Smiley Cookie!

Modifying New and Past Instances of a Type

Now then, if we modify, add, or delete a member in MintChocolateChunk.prototype, Chocolate.prototype, Coffee.prototype, or VanillaBean.prototype, the change will be evident in any instance, both new and old, that inherits the member. So for example, if we change MintChocolateChunk.prototype.bittersweet[2] from "Callebaut" to "Lindt" after creating mintChocolateChunk, its bittersweet member reflects the change as Figure 5-10 displays:

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;
var mintChocolateChunk = new MintChocolateChunk();
console.dir(mintChocolateChunk);

MintChocolateChunk.prototype.bittersweet[2] = "Lindt";
console.dir(mintChocolateChunk);
Changing MintChocolateChunk.prototype.bittersweet[2] from "Callebaut" to "Lindt" after creating mintChocolateChunk still changes its bittersweet member.

Figure 5-10. Changing MintChocolateChunk.prototype.bittersweet[2] from "Callebaut" to "Lindt" after creating mintChocolateChunk still changes its bittersweet member.

Now let's add four chopped Heath Bars to VanillaBean.prototype in order to change vanilla to Ben & Jerry's Vanilla Heath Bar Crunch and coffee to Ben & Jerry' Coffee Heath Bar Crunch. However, as Figure 5-11 displays, by doing so we also wind up with Chocolate Heath Bar Crunch and Mint Chocolate Chip Heath Bar Crunch, because everything that inherits from VanillaBean gets the heathBars member!

var VanillaBean = function(vanilla, cinnamon) {
  this.vanilla = [1, "bean", vanilla ? vanilla : "Madagascar Bourbon"];
  cinnamon && (this.cinnamon = [1, "stick", "Saigon"]);
};
VanillaBean.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var Coffee = function(coffee) {
  this.coffee = coffee || [1/4, "cup, coarsely ground", "Starbucks Espresso"];
};
Coffee.prototype = new VanillaBean();
var Chocolate = function(cocoa, bittersweet) {
  this.cocoa = cocoa || [3/16, "cup", "Callebaut"];
  this.bittersweet = bittersweet || [1 + 1/2, "cup", "Callebaut"];
};
Chocolate.prototype = new VanillaBean();
Chocolate.prototype.yolks = [4];
var MintChocolateChunk = function(mint) {
  this.mint = mint || [1, "cup", "fresh mint leaves"];
};
MintChocolateChunk.prototype = new Chocolate();
MintChocolateChunk.prototype.vanilla = [1/3, "bean", "Madagascar Bourbon"];
MintChocolateChunk.prototype.bittersweet[0] = 1;
delete MintChocolateChunk.prototype.cocoa;
var vanilla = new VanillaBean();
var coffee = new Coffee();
var chocolate = new Chocolate();
var mintChocolateChunk = new MintChocolateChunk();

VanillaBean.prototype.heathBars = [4, "Heath Bars, chopped in chunks"];
console.dir(vanilla);
console.dir(coffee);
console.dir(chocolate);
console.dir(mintChocolateChunk);
Adding four Heath Bars to VanillaBean.prototype has unintended effects, which are that the three other objects also include Heath Bars.

Figure 5-11. Adding four Heath Bars to VanillaBean.prototype has unintended effects, which are that the three other objects also include Heath Bars.

Sharing a Prototype but Forgoing the Chain

One drawback of chaining prototypes is that own members turn into inherited members. For example, linking MintChocolateChunk.prototype to Chocolate.prototype by invoking Chocolate() put cocoa and bittersweet members on MintChocolateChunk.prototype, so we had to delete MintChocolateChunk.prototype.cocoa and modify MintChocolateChunk.prototype.bittersweet[0].

One way around this bugaboo would be to have two constructors share a prototype object, thereby dispensing with the chain link. Let's look at an example: to make blueberry, strawberry, mango, or raspberry ice cream, I'll typically puree one of the two cups of fresh fruit. Then cut the half & half from 2 cups to 1 and the yolks from 6 to 3. With this in mind, we can have constructors for blueberry and strawberry ice cream share a parent prototype object, as shown next.

Note that fraises des bois are smaller, sweeter, and more flavorful than traditional strawberries. Create an instance of Blueberry() and Strawberry(), verifying your work with Figure 5-12.

var Berry = function() {}
Berry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [3],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};

var Blueberry = function(blueberry, lemon) {
  this.blueberry = [2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
  this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
};
Blueberry.prototype = Berry.prototype;

var Strawberry = function(strawberry) {
  this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"];
};
Strawberry.prototype = Berry.prototype;
var blueberry = new Blueberry();
var strawberry = new Strawberry();
console.dir(blueberry);
console.dir(strawberry);
There's no prototype chain link between Strawberry.prototype and Blueberry.prototype.

Figure 5-12. There's no prototype chain link between Strawberry.prototype and Blueberry.prototype.

So, forgoing the prototype chain link prevented our having to delete Strawberry.prototype.blueberry and Strawberry.prototype.freshLemonJuice members as in the following sample and Figure 5-13, where the strawberry recipe is based on the blueberry recipe:

var Blueberry = function(blueberry, lemon) {
  this.blueberry = [2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
  this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
};
Blueberry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [3],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var Strawberry = function(strawberry) {
  this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"];
};
Strawberry.prototype = new Blueberry();
delete Strawberry.prototype.blueberry;
delete Strawberry.prototype.freshLemonJuice;
var blueberry = new Blueberry();
var strawberry = new Strawberry();
console.dir(blueberry);
console.dir(strawberry);
Having to delete unwanted Strawberry.prototype.blueberry and Strawberry.prototype.freshLemonJuice members

Figure 5-13. Having to delete unwanted Strawberry.prototype.blueberry and Strawberry.prototype.freshLemonJuice members

That's the good news. Now for the bad. Since Blueberry.prototype and Strawberry.prototype are the same object, there's no way to add, delete, or modify inherited members for Strawberry() without identically changing inherited embers for Blueberry(), and vice versa. To illustrate the point, try the following sample, verifying your work with Figure 5-14:

var Berry = function() {}
Berry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
yolks: [3],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};

var Blueberry = function(blueberry, lemon) {
  this.blueberry = [2, "cup", blueberry ? blueberry : "Maine wild blueberries"];
  this.freshLemonJuice = [2, "tsp", lemon ? lemon : "Meyer"];
};
Blueberry.prototype = Berry.prototype;

var Strawberry = function(strawberry) {
  this.strawberry = [2, "cup", strawberry ? strawberry : "fraises des bois"];
};
Strawberry.prototype = Berry.prototype;
var blueberry = new Blueberry();
var strawberry = new Strawberry();
Blueberry.prototype.cinnamon = [1, "stick", "Saigon"];
console.dir(blueberry);
console.dir(strawberry);
Adding a cinnamon member to Blueberry.prototype is no different from adding it to Strawberry.prototype or Berry.prototype since they're the same object.

Figure 5-14. Adding a cinnamon member to Blueberry.prototype is no different from adding it to Strawberry.prototype or Berry.prototype since they're the same object.

Adding an Empty Chain Link

Both of the previous patterns have a downside. Chaining prototypes turns own members into inherited members, adding those to the child constructor's prototype. Forgoing the prototype chain solves that problem but then effectively prevents us from adding, deleting, or modifying prototype members.

Hmm. Would there be a way for a child constructor to inherit members from a parent constructor's prototype yet have its own, blank prototype to add inherited members to?

Yup, but you'll need to be ten toes in for a few moments to get it. First we'll create a helper function named extend that takes two parameters:

  • child will be the constructor we want to have a blank prototype.

  • parent will be a constructor we want the child to inherit members from.

var extend = function (child, parent) {
};

Now we'll create a constructor named Proxy() with an empty code block. In this way, when we invoke Proxy() with new, it will return an empty object.

var extend = function (child, parent) {
  var Proxy = function () {};
};

Now just as we did for Strawberry.prototype and Blueberry.prototype, we'll have Proxy.prototype refer to parent.prototype. That is to say, there's no prototype chain between Proxy.prototype and parent.prototype. Rather, they're just two names for the same object.

var extend = function (child, parent) {
  var Proxy = function () {};
  Proxy.prototype = parent.prototype;
};

Now just as we linked MintChocolateChunk.prototype to Chocolate.prototype with the assignment expression MintChocolateChunk.prototype = new Chocolate(), we'll link child.prototype to parent.prototype with the assignment expression child.prototype = new Proxy(). Note that in terms of creating a link from child.prototype to parent.prototype, the expressions new parent() and new Proxy() would be equivalent because parent.prototype and Proxy.prototype refer to the same object. However, since Proxy() has an empty body, it returns an empty object linked to parent.prototype. It's that empty object that is assigned to child.prototype.

var extend = function (child, parent) {
  var Proxy = function () {};
  Proxy.prototype = parent.prototype;
  child.prototype = new Proxy();
};

Now it's inefficient to create Proxy() every time we invoke extend(), so by way of a self-invoking function, we'll save Proxy() to a closure. Just nod knowingly for now; we'll cover self-invoking functions and closures in Chapter 6.

var extend = (function () {
  var Proxy = function () {};
  return function (child, parent) {
    Proxy.prototype = parent.prototype;
    child.prototype = new Proxy();
  }
}());

Chaining prototypes or using extend() overwrites the default prototype and its constructor member, which simply refers to the function that constructed the object, so let's add a constructor member to child.prototype:

var extend = (function () {
  var Proxy = function () {};
  return function (child, parent) {
    Proxy.prototype = parent.prototype;
child.prototype = new Proxy();
    child.prototype.constructor = child;
  }
}());

The downside of doing so is that while the default constructor member would not be enumerated in a for in loop, the one you add by hand would be enumerated. For this reason, you may want to forgo adding a constructor member, but I have added it here so you can say you've seen one in action. Note that JavaScript does not need the constructor member for the prototype chain, instanceof operator, or any feature to work.

A common practice is to add a static member to child (not to child.prototype) referring to parent.prototype. Doing so provides a way to query a type's parent (known as its superclass). That's not something you'll do as a beginner. Still, you'll see it around, so let's add one named donor:

var extend = (function () {
  var Proxy = function () {};
  return function (child, parent) {
    Proxy.prototype = parent.prototype;
    child.prototype = new Proxy();
    child.prototype.constructor = child;
    child.donor = parent.prototype;
  }
}());

Now that we have extend() written, let's use it to create a blank prototype for a CherryGarcia() constructor that is chained to Strawberry.prototype. In this way, instances of CherryGarcia will inherit members from Strawberry.prototype but not the strawberry member created in the body of Strawberry(). Cherries are sweeter than strawberries, so let's override Strawberry.prototype.sugar by adding a sugar member to CherryGarcia.prototype.

Now for the moment of truth. Create instances of Strawberry() and CherryGarcia(), passing them to Firebug's console.dir() method. Then verify your work with Figure 5-15.

Cherry Garcia. Mmmh.

var extend = (function () {
  var Proxy = function () {};
  return function (child, parent) {
    Proxy.prototype = parent.prototype;
    child.prototype = new Proxy();
    child.prototype.constructor = child;
    child.donor = parent.prototype;
  }
}());
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);

CherryGarcia.prototype.sugar = [9/16, "cup"];
var strawberry = new Strawberry();
var cherryGarcia = new CherryGarcia();
console.dir(strawberry);
console.dir(cherryGarcia);
cherryGarcia inherits members only from Strawberry.prototype.

Figure 5-15. cherryGarcia inherits members only from Strawberry.prototype.

Stealing a Constructor

With extend(), we can chain the prototype of one constructor to the prototype of another. But what if we want to fully duplicate a constructor? In other words, say we want to inherit members from its prototype and borrow all its other members that aren't in the prototype. We'd do the former with extend() and the latter invoking the parent constructor's apply() method from within the child constructor, passing this and an array of parameters.

For example, if we wanted to make CherryGarcia() be a Cherry() clone that adds Callebaut bittersweet chunks, we'd chain CherryGarcia.prototype to Cherry.prototype with extend(). Then invoke Cherry.apply() from within CherryGarcia(), passing this and the cherry parameter as in the following sample and Figure 5-16:

var extend = (function () {
  var Proxy = function () {};
  return function (child, parent) {
    Proxy.prototype = parent.prototype;
    child.prototype = new Proxy();
    child.prototype.constructor = child;
    child.donor = parent.prototype;
  }
}());
var Cherry = function(cherry) {
  this.cherries = [2, "cup, pitted and halved", cherry ? cherry : "Bing"];
};
Cherry.prototype = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [9/16, "cup"],
  yolks: [3],

  vanilla: [1, "bean", "Madagascar Bourbon"]

};
var CherryGarcia = function(cherry, bittersweet) {
  Cherry.apply(this, [cherry]);
  this.bittersweet = [1, "cup, coarsely chopped", bittersweet ? bittersweet : "Callebaut"];
};
extend(CherryGarcia, Cherry);

var cherry = new Cherry();

var cherryGarcia = new CherryGarcia();
console.dir(cherry);
console.dir(cherryGarcia);
CherryGarcia() is a Cherry() clone that adds Callebaut bittersweet chunks.

Figure 5-16. CherryGarcia() is a Cherry() clone that adds Callebaut bittersweet chunks.

Prototypal Inheritance

Oftentimes you will want to create one object that is pretty similar to another. The techniques we saw previously can do this, but there was a fair amount of work involved when writing all the constructors and chaining their prototypes if there are lots of similarities between the objects. For circumstances like this, we'll forgo the previous classical inheritance and turn to prototypal inheritance instead, which clones from prototypes, rather than using inheritance such as that we saw earlier. Though ECMAScript 5 defines an Object.create() function for prototypal inheritance, no browsers yet implement it. So, we'll write our own prototypal inheritance function named clone() while we wait for Firefox, Safari, Opera and eventually Internet Explorer to implement Object.create().

Note

The beta versions of the next major releases for Internet Explorer, Firefox, Chrome, and Safari all support Object.create(), so it might not be so long before it's in general use.

clone() works with one parameter named donor, which contains the object you want to clone. Similar to extend(), we'll create an empty constructor function named Proxy(). Unlike extend(), we'll set Proxy.prototype to donor rather than to donor.prototype and return an empty object, which will inherit both own and inherited members from donor. We'll then add whatever additional members we need to the empty object.

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

So, say we have a quart of banana ice cream and want to make a quart of Ben & Jerry's Chunky Monkey based on the contents of the banana ice cream. We'd do so with the help of clone() as in the following sample and Figure 5-17:

var clone = function (donor) {
  var Proxy = function () {};
  Proxy.prototype = donor;
  return new Proxy();
};
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 chopped", "Callebaut"];
console.dir(banana);
console.dir(chunkyMonkey);
Prototypal inheritance is much simpler than classical.

Figure 5-17. Prototypal inheritance is much simpler than classical.

Now then, Object.create() will take an optional second parameter when it eventually arrives, which is an object containing members to add to the clone. So, let's define a second function named emulate() that does the same thing while we wait for better days:

var emulate = function (donor, more) {
  var Proxy = function () {}, child, m;
  Proxy.prototype = donor;
  child = new Proxy();
  for (var m in more) {
    child[m] = more[m];
  }
  return child;
};

Now say we've made a quart of chocolate ice cream and want to make a quart of Ben & Jerry's New York Super Fudge Chunk. We can do so by passing the chocolate quart plus the additional ingredients to emulate(). Try it, verifying your work with Figure 5-18:

var emulate = function (donor, more) {
  var Proxy = function () {}, child, m;
  Proxy.prototype = donor;
  child = new Proxy();
  for (var m in more) {
    child[m] = more[m];
  }
  return child;
};
var chocolate = {
  heavyCream: [1, "cup", "Organic Valley"],

  halfHalf: [2, "cup", "Organic Valley"],

  sugar: [5/8, "cup"],
  yolks: [6],
  cocoa: [3/8, "cup", "Callebaut, Dutch process"],
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var newYorkSuperFudgeChunk = emulate(chocolate, {
  pecans: [1/4, "cup, coarsely chopped"],

  walnuts: [1/4, "cup, coarsely chopped"],

  almonds: [1/4, "cup, coarsely chopped"],
  whiteChocolate: [1/3, "cup, coarsely chopped", "Callebaut"],
  bittersweetChocolate: [1/3, "cup, coarsely chopped", "Callebaut"]
});
console.dir(chocolate);
console.dir(newYorkSuperFudgeChunk);
Adding members in prototypal inheritance

Figure 5-18. Adding members in prototypal inheritance

Cloning Members

Another way to clone an object is to do a deep copy of its members. Unlike emulate() shown earlier, which does a shallow copy of members (that is, members of the object type are copied by reference), a deep copy recursively clones those. Let's write a helper function named cloneMembers() that will clone an object by doing a deep copy of its members, deferring the explanation of recursion until Chapter 6.

var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    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;
};

Now if we want to make a quart of Ben & Jerry's Coffee Heath Bar Crunch from Vanilla Heath Bar Crunch, we'd clone the latter by doing a deep copy of its members. Then add a coffee member, verifying our work with Figure 5-19. Note that coffeeHeathBarCrunch does not inherit members from vanillaHeathBarCrunch via the prototype chain. Rather, coffeeHeathBarCrunch has deep copies of vanillaHeathBarCrunch members. So, no prototype chain this time.

var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    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"];
console.dir(vanillaHeathBarCrunch);

console.dir(coffeeHeathBarCrunch);
Doing a deep copy of members

Figure 5-19. Doing a deep copy of members

Mixins

Finally, we can create an object by doing a deep copy of the members of two or more objects. Doing so is called a mixin. So, we'll write a merge() function that takes an array of mixins named mixins and optionally a donee to clone members to. Note that merge() will have cloneMembers() do the deep copy.

var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    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 merge = function (mixins, donee) {
  var i, j, donee = donee || {};
  for (i = 0, j = mixins.length; i < j; i ++) {

    cloneMembers(mixins[i], donee);

  }
  return donee;
};

Now let's create some mixins. Throughout this chapter, we've used a French sweet cream base; other kinds of ice cream bases include Philadelphia, which contains no yolks, and Italian gelato, which has a higher yolk to cream ratio than French and therefore delivers a denser ice cream. In addition to those bases, we'll create some mixins for flavors.

var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    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 merge = function (mixins, donee) {
  var i, j, donee = donee || {};
  for (i = 0, j = mixins.length; i < j; i ++) {
    cloneMembers(mixins[i], donee);
  }
  return donee;
};
var french = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var philly = {

  heavyCream: [2, "cup", "Organic Valley"],

  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"]
};
var gelato = {
  halfHalf: [3, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var vanilla = {
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var heathBar = {
  heathBars: [4, "bars, coarsely chopped"]
};
var coffee = {
  coffee: [1/4, "cup, coarsely ground", "Starbucks Espresso"]
};

Having done so, we'll create Italian-style Coffee Heath Bar Crunch, Philadelphia-style Coffee, and French-style Vanilla Heath Bar Crunch, verifying our work with Figure 5-20:

var cloneMembers = function (donor, donee) {
  donee = donee || {};
  for (var m in donor) {
    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 merge = function (mixins, donee) {
  var i, j, donee = donee || {};
  for (i = 0, j = mixins.length; i < j; i ++) {
    cloneMembers(mixins[i], donee);
  }
  return donee;
};
var french = {
  heavyCream: [1, "cup", "Organic Valley"],
  halfHalf: [2, "cup", "Organic Valley"],
sugar: [5/8, "cup"],
  yolks: [6]
};
var philly = {
  heavyCream: [2, "cup", "Organic Valley"],
  halfHalf: [1, "cup", "Organic Valley"],
  sugar: [5/8, "cup"]
};
var gelato = {
  halfHalf: [3, "cup", "Organic Valley"],
  sugar: [5/8, "cup"],
  yolks: [6]
};
var vanilla = {
  vanilla: [1, "bean", "Madagascar Bourbon"]
};
var heathBar = {
  heathBars: [4, "bars, coarsely chopped"]
};
var coffee = {
  coffee: [1/4, "cup, coarsely ground", "Starbucks Espresso"]
};

var coffeeHeathBarCrunch = merge([gelato, vanilla, coffee, heathBar]);
console.dir(coffeeHeathBarCrunch);
var coffee = merge([philly, vanilla, coffee]);
console.dir(coffee);
var vanillaHeathBarCrunch = merge([french, vanilla, heathBar]);
console.dir(vanillaHeathBarCrunch);
Creating Italian, Philadelphia, and French ice creams with a mixin implementation

Figure 5-20. Creating Italian, Philadelphia, and French ice creams with a mixin implementation

Summary

In this chapter, we explored inheriting members by way of classical, prototypal, deep copy, and mixin implementations. However, more often than not, it's methods, which is to say functions, that are inherited. We'll cover that and other function essentials in Chapter 6.

Have a well-deserved scoop or two of your favorite ice cream, and I'll see you there.

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

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