On the Art of Summoning Servants and Critters, Or Understanding The Basics of JavaScript Objects

Things are ideas,
ideas are abstractions,
abstractions are objects,
objects are things.

That's the secret of JavaScript-mancy
  
        - Branden Iech,
        Meditations

An Army of Objects

Hello JavaScriptmancer! It is time to get an introduction to the basics of objects in JavaScript. In this chapter you’ll learn the beauty of the object initializer and the nice improvements ES6 brings to objects. If you think that you already know this stuff, think twice! There is more than one surprise in this chapter and I promise that you’ll learn something new by the end of it.

Let’s get started! We’ll start by concentrating our efforts in the humble object initializer. This will provide a foundation that we can use later when we come to the tome of object-oriented programming in JavaScript and prototypical inheritance.

Objects it is!

Object Initializers (a.k.a. Object Literals)

The simplest way to create an object in JavaScript is to use an object initializer:

1 var critter = {}; // {} is an empty object initializer

You can add properties and methods inside your object initializer to your heart’s content:

 1 critter = {
 2   position: {x: 0, y: 0},
 3   movesTo: function (x, y){
 4     console.log(this + ' moves to (' + x + ',' + y + ')');
 5     this.position.x = x;
 6     this.position.y = y;
 7   },
 8   toString: function(){
 9     return 'critter';
10   },
11   hp: 40
12 }

And, of course, if you call a method within the critter object it behaves as you have come to expect from any good self-respecting method:

1 critter.moveTo(10, 10);
2 // => critter moves to (10,10)

As you saw in the introduction of the book, you can augment any11 object at any time with new properties:

1 critter.damage = 1;
2 critter.attacks = function(target) {
3   console.log(this + ' rabidly attacks ' + target + 
4               ' with ' + this.damage + ' damage');
5   target.hp-=this.damage;
6 };

And use these new abilities to great devastation:

1 var rabbit = {hp:10, toString: function(){return 'rabbit';}};
2 
3 critter.attacks(rabbit);
4 // => critter rabidly attacks rabbit with 1 damage

Alternatively, you can access any property and method within an object by using the indexing notation via []:

1 critter['attacks'](rabbit);
2 // => critter rabidly attacks rabbit with 1 damage

Although a little bit more verbose, this notation lets you use special characters as names of properties and methods:

 1 critter['sounds used when communicating'] = [
 2   'beeeeeh', 'grrrrr', 'tjjiiiiii'
 3 ];
 4 critter.saysSomething = function(){
 5   var numberOfSounds = this['sounds used when communicating'].length,
 6       randomPick = Math.floor(Math.random()*numberOfSounds);
 7 
 8   console.log(this['sounds used when communicating'][randomPick]);
 9 };
10 
11 critter.saysSomething();
12 // => beeeeeeh (random pick)
13 critter.saysSomething();
14 // => tjjiiiii (random pick)

As you can see in many of the examples above, you can use the this keyword to reference the object itself and thus access other properties within the same object.

Getters and Setters

Getters and setters are an often overlooked feature within object initializers. You’ll even find fairly seasoned JavaScript developers that don’t know about their existence. They work exactly like C# properties and look like this:

 1 var mouse = {
 2   strength: 1,
 3   dexterity: 1,
 4   get damage(){ 
 5     return this.strength*die20() + this.dexterity*die8();
 6   },
 7   attacks: function(target){
 8     console.log(this + ' ravenously attacks ' + target + 
 9                 ' with ' + this.damage + ' damage!');
10     target.hp-=this.damage;
11   },
12   toString: function() { return 'mouse';}
13 }

Notice the strange get damage() function-like thingy? That’s a getter. In this case, it represents the read-only property damage that is calculated from other two properties strength and dexterity.

1 mouse.attacks(rabbit);
2 // => mouse ravenously attacks rabbit with 19 damage!
3 mouse.attacks(rabbit);
4 // => mouse ravenously attacks rabbit with 15 damage!

Getters are extremely useful when you need to define computed properties, that is, properties described in terms of other existing properties. They save you from needing to keep additional and unnecessary state that brings the additional burden of keeping it in sync with the properties it depends on (in this case strength and dexterity).

We can also use a backing field to perform additional steps or validation:

 1 var giantBat = {
 2   _hp: 1,
 3   get hp(){ return this._hp;},
 4   set hp(value){ 
 5     if (value < 0) { 
 6       console.log(this + ' dies :(')
 7       this._hp = 0;
 8     } else { 
 9       this._hp = value;
10     }
11   },
12   toString: function(){
13     if (this.hp > 0){
14       return 'giant bat';
15     } else {
16       return 'a dead giant bat';
17     }
18   }
19 };

In this example we ensure that the _hp property of the giant bat cannot go below 0 (because you can’t be deader than dead, unless you are a necromancer that is):

1 mouse.attacks(giantBat);
2 // => "mouse ravenously attacks giant bat with 23 damage!"
3 // => "giant bat dies :("
4 console.log(giantBat.toString());
5 // => a dead giant bat

Method Overloading

Method overloading within object initializers works just like with functions. As we saw in the previous chapter, if you try to overload a method following the same pattern that you are accustomed to in C#:

 1 var venomousFrog = {
 2   toString: function(){
 3     return 'venomous frog';
 4   },
 5   jumps: function(meters){ 
 6     console.log(this + ' jumps ' + meters + ' meters in the air');
 7   },
 8   jumps: function(arbitrarily) { 
 9     console.log( this + ' jumps ' + arbitrarily);
10   }
11 };

You’ll just succeed in overwriting the former jump method with the latter:

1 venomousFrog.jumps(10);
2 // => venomous frog jumps 10
3 // ups we have overwritten a the first jumps method

Instead, use any of the patterns that you saw in the previous chapter to achieve method overloading. For instance, you can inspect the arguments being passed to the jump function:

1 venomousFrog.jumps = function(arg){
2   if (typeof(arg) === 'number'){
3     console.log(this + ' jumps ' + arg + ' meters in the air');
4   } else {
5     console.log( this + ' jumps ' + arg);            
6   }
7 };

This provides a naive yet functioning implementation of method overloading:

1 venomousFrog.jumps(10);
2 // => venomous frog jumps 10 meters
3 venomousFrog.jumps('wildly in front of you')
4 // => venomous frong jumps wildly in front of you

Creating Objects With Factories

Creating one-off objects through object initializers can be tedious, particularly whenever you need more than one object of the same “type”. That’s why we often use factories13 to encapsulate object creation:

 1 function monster(type, hp){
 2   return {
 3     type: type,
 4     hp: hp || 10,
 5     toString: function(){return this.type;},
 6     position: {x: 0, y: 0},
 7     movesTo: function (x, y){
 8       console.log(this + ' moves to (' + x + ',' + y + ')');
 9       this.position.x = x;
10       this.position.y = y;
11     }
12   };
13 }

Once defined, we can just use it to instantiate new objects as we wish:

1 var tinySpider = monster('tiny spider', /* hp */ 1);
2 tinySpider.movesTo(1,1);
3 // => tiny spider moves to (1,1)
1 var giantSpider = monster('giant spider', /* hp */ 200);
2 giantSpider.movesTo(10,10);
3 // => giant spider moves to (10,10);

There’s a lot of cool things that you can do with factories in JavaScript. Some of them you’ll discover when you get to tome of OOP where we will see an alternative to classical inheritance in the shape of object composition via mixins. In the meantime let’s take a look at how to achieve data privacy.

Data Privacy in JavaScript

You may have noticed by now that there’s no access modifiers in JavaScript, no private, public nor protected keywords. That’s because every property is public, that is, there is no way to declare a private property by using a mere object initializer. You need to rely on additional patterns with closures to achieve data privacy, and that’s where factories come in handy.

Imagine that we have the previous example of our monster but now we don’t want to reveal how we have implemented positioning. We would prefer to hide that fact from prying eyes and object consumers. If we decide to change it in the future, for a three dimensional representation, polar coordinates or who knows what, it won’t break any clients of the object. This is part of what I call intentional programming, every decision that you make, the interface that you build, the parts that you choose to remain hidden or public, represent your intentions on how a particular object or API should be used. Be mindful and intentional when you write code. Back to the monster:

 1 function stealthyMonster(type, hp){
 2   var position = {x: 0, y: 0};
 3   
 4   return {
 5     type: type,
 6     hp: hp || 10,
 7     toString: function(){return 'stealthy ' + this.type;},
 8     movesTo: function (x, y){
 9       console.log(this + ' moves stealthily to (' + x + ',' + y + ')\
10 ');
11       // this function closes over (or encloses) the position 
12       // variable position is NOT part of the object itself, 
13       // it's a free variable that's why you cannot access it 
14       // via this.position
15       position.x = x;
16       position.y = y;
17     }
18   };
19 }

Let’s take a closer look to that example. We have extracted the position property outside of the object initializer and inside a variable within the stealthyMonster scope (remember that functions create scopes in JavaScript). At the same time, we have updated the movesTo function, which creates its own scope, to refer to the position variable within the outer scope effectively creating a closure.

Because position is not part of the object being returned, it is not accessible to clients of the object through the dot notation. Because the movesTo becomes a closure it can access the position variable within the outside scope. In summary, we got ourselves some data privacy:

1 var darkSpider = stealthyMonster('dark spider');
2 console.log(darkSpider.position)
3 // now position is completely private
4 // => undefined
5 
6 darkSpider.movesTo(10,10);
7 // => stealthy dark spider moves stealthily to (10,10)

ES6 Improves Object Initializers

ES6 brings some improvements to object initializers that reduce the amount of code needed to create a new object. For instance, with ES6 you can declare methods within objects using shorthand syntax:

 1 let sugaryCritter = {
 2   position: {x: 0, y: 0},
 3   // from movesTo: function(x, y) to...
 4   movesTo(x, y){
 5     console.log(`${this} moves to (${x},${y})`);
 6     this.position.x = x;
 7     this.position.y = y;
 8   },
 9   // from toString: function() to...
10   toString(){
11     return 'sugary ES6 critter';
12   },
13   hp: 40
14 };
15 
16 sugaryCritter.movesTo(10, 10);
17 // => sugary ES6 critter moves to (10, 10)

As you can appreciate from the movesTo and toString methods in this example above, using shorthand notation lets you skip the function keyword and collapse the parameters of a function directly after its name.

Additionally you can apply shorthand syntax to object properties. When you write factory functions you’ll often follow a pattern where you initialize object properties based on the arguments passed to the factory function:

1 function simpleMonster(type, hp = 10){
2   return {
3     type: type,
4     hp: hp
5   };
6 }

Where you have a little bit of redundant code in type: type and hp: hp. Property shorthand syntax removes the need to repeat yourself by letting you write the property/value pair only once. So that the previous example turns into a much terser factory method:

1 function simpleMonster(type, hp = 10){
2   return {
3     // with property shorthand we avoid the need to repeat 
4     // the name of the variable twice (type: type)
5     type,
6     hp
7   };
8 }

And here you have a complete example where we use both method and property shorthand to get the ultimate sugary monster:

 1 function sugaryStealthyMonster(type, hp = 10){
 2   let position = {x: 0, y: 0};
 3   
 4   return {
 5     // with property shorthand we avoid the need to repeat 
 6     // the name of the variable twice (type: type)
 7     type,
 8     hp,
 9     toString(){return `stealthy ${this.type}`;},
10     movesTo(x, y){
11       console.log(`${this} moves stealthily to (${x},${y})`);
12       position.x = x;
13       position.y = y;
14     }
15   };
16 }
17 
18 let sugaryOoze = sugaryStealthyMonster('sugary Ooze', /*hp*/ 500);
19 sugaryOoze.movesTo(10, 10);
20 // => stealthy sugary Ooze moves stealthily to (10,10)

Finally, with the advent of ES6 you can use any expression as the name of an object property. That is, you are no longer limited to normal names or using the square brackets notation that handles special characters. From ES6 onwards you’ll be able to use any expression and the JavaScript engine will evaluate it as a string (with the exception of ES6 symbols which we’ll see in the next section). Take a look at this:

 1 let theArrow = () => 'I am an arrow';
 2 
 3 let crazyMonkey = {
 4   // ES5 valid
 5   name: 'Kong',
 6   ['hates!']: ['mario', 'luigi'],
 7 
 8   // ES6 computed property names
 9   [(() => 'loves!')()]: ['bananas'],
10   [sugaryOoze.type]: sugaryOoze.type
11   // crazier yet
12   [theArrow]: `what's going on!?`,
13 }

This example let’s you appreciate how any expression is valid. We’ve used the result of evaluating a function (() => 'loves!')(), a property from another object sugaryOoze.type and even an arrow function theArrow as property names. If you inspect the object itself, you can see how each property has been intrepreted as a string:

 1 console.log(crazyMonkey);
 2 // => [object Object] {
 3 //    function theArrow() {
 4 //      return 'I am an arrow';
 5 //    }: "what's going on!?",
 6 //    hates!: ["mario", "luigi"],
 7 //    loves!: ["bananas"],
 8 //    name: "Kong",
 9 //  sugary Ooze: "sugary Ooze"
10 // }

And you can retrieve them with the [](indexing) syntax:

1 console.log(crazyMonkey[theArrow]);
2 // => "what's going on!?"

Use cases for this particular feature? I can only think of some pretty far-fetched edge cases for dynamic creation of objects on-the-fly. That and using symbols as property names wich gracefully brings us to ES6 symbols and how to take advantage of them to simulate data privacy.

ES6 Symbols and Data Privacy

Symbols are a new type in JavaScript. They were conceived to represent constants and to be used as identifiers for object properties. The specification even describes them as the set of all non-string values that may be used as the key of an object property 14. They are immutable and can have a description associated to them.

You can create a symbol using the Symbol function:

1 let anUndescriptiveSymbol = Symbol();
2 console.log(anUndescriptiveSymbol);
3 // => [object Symbol]
4 console.log(typeof anUndescriptiveSymbol);
5 // => symbol
6 console.log(anUndescriptiveSymbol.toString());
7 // => Symbol()

And you can add a description to the symbol by passing it as an argument to the same function. This will be helpful for debugging since the toString method will display that description:

1 // you can add a description to the Symbol
2 // so you can identify a symbol later on
3 let up = Symbol('up');
4 console.log(up.toString());
5 // => Symbol(up)

Each symbol is unique and immutable, so even if we create two symbols with the same description, they’ll remain two completely different symbols:

1 // each symbol is unique and immutable
2 console.log(`Symbol('up') === Symbol('up')?? 
3   ${Symbol('up') === Symbol('up')}`);
4 // => Symbol('up') === Symbol('up')?? false

ES6 symbols offer us a new approach to data privacy in addition to closures. Properties that use a symbol as name (or key) can only be accessed by a reference to that symbol (the very same symbol used to identify the property). Because of this special characteristic, if you don’t expose a symbol to the outer world you have provided yourself with data privacy. Let’s see how this works in practice:

 1 function flyingMonster(type, hp = 10){
 2   let position = Symbol('position');
 3   
 4   return {
 5     [position]: {x: 0, y: 0},
 6     type,
 7     hp,
 8     toString(){return `stealthy ${this.type}`;},
 9     movesTo(x, y){
10       console.log(`${this} flies like the wind from` +
11        `(${this[position].x}, ${this[position].y}) to (${x},${y})`);
12       this[position].x = x;
13       this[position].y = y;
14     }
15   };
16 }
17 
18 let pterodactyl = flyingMonster('pterodactyl');
19 pterodactyl.movesTo(10,10);
20 // => stealthy pterodactyl flies like the wind from (0,0) to (10,10)

Since outside of the flyingMoster function we don’t have a reference to the symbol position (it is scoped inside the function), we cannot access the position property:

1 console.log(pterodactyl.position);
2 // => undefined

And because each symbol is unique we cannot access the property using another symbol with the same description:

1 console.log(pterodactyl[Symbol('position')]);
2 // => undefined

If everything ended here the world would be perfect, we could use symbols for data privacy and live happily ever after. However, there’s a drawback: The JavaScript Object prototype provides the getOwnPropertySymbols method that allows you to get the symbols used as properties within any given object. This means that after all this trouble we can access the position property by following this simple procedure:

1 var symbolsUsedInObject = Object.getOwnPropertySymbols(pterodactyl);
2 var position = symbolsUsedInObject[0];
3 console.log(position.toString());
4 // => Symbol(position)
5 // Got ya!
6 
7 console.log(pterodactyl[position]);
8 // => {x: 10, y: 10}
9 // ups!

So you can think of symbols as a soft way to implement data privacy, where you give a clearer intent to your code, but where your data is not truly private. This limitation is why I still prefer using closures over Symbols.

Concluding

In this chapter you learned the most straightforward way to work with objects in JavaScript, the object initializer. You learned how to create objects with properties and methods, how to augment existing objects with new properties and how to use getters and setters. We also reviewed how to overload object methods and ease the repetitive creation of objects with factories. We wrapped factories with a pattern for achieving data privacy in JavaScript through the use of closures.

You also learnt about the small improvements that ES6 brings to object initializers with the shorthand notation for both methods and properties. We wrapped the chapter with a review of the new ES6 Symbol type and its usage for attaining a soft version of data privacy.

Exercises