barbarian meets coding

WebDev, UX & a Pinch of Fantasy

Mastering the Arcane Art of JavaScript-Mancy for C# Developers: On Summoning Servants and Critters, or the Basics of Objects in JavaScript

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

The Very Basics of Objects in JavaScript

Hello JavaScriptmancer! It is time to get an introduction to the basics of objects in JavaScript. In this article 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 are more than one surprise in this article 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 to provide some foundation that we can use later when we come to object-oriented programming in JavaScript and prototypical inheritance.

Objects it is!

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

You can experiment with all examples in this article directly within this jsBin or downloading the source code from GitHub.

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

1
var critter = {};

You can add properties and methods to your object initializer as desired:

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

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

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

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

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

And use these new abilities to great devastation:

1
2
3
4
var rabbit = {hp:10, toString: function(){return 'rabbit';}};

critter.attacks(rabbit);
// => critter rabidly attacks rabbit with 1 damage

You can also use special characters as names for your properties by taking advantage of the [] (indexing) notation:

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

critter.saysSomething();
// => beeeeeeh (random pick)
critter.saysSomething();
// => 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.

JavaScript Arcana: This in JavaScript

From my experience, this is the biggest source of problems for a C# developer that moves to JavaScript. We are so accustomed to work with classes and objects in C#, to be able to blindly rely in the value of this, that when we move to JavaScript where the behavior of this is so completely undependable we explode in frustration and anger.

Since this is such a big part of the JavaScript Arcana, I devote a whole article to demystifying it for you. For now, just remember that when calling a method on a object using the dot notation, like in critter.moveTo, the value of this is mostly2 trustworthy.

Getters and Setters

Getters and setters are an often overlooked feature within object initializers since ES5. They work exactly like C# properties and look like this:

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

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

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

We can use a backing field to perform additional steps or validation in the same way we are familiar to in C#:

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

In this example we ensure that the _hp of the giant bat cannot go below 0:

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

You may have noticed that I have created a couple of new objects for these two examples instead of augmenting my beloved critter. Well, there was a reason for that. You cannot augment objects with getters and setters in the same way that you add other properties. In this special case, you need to rely in the Object.defineProperty or Object.defineProperties both methods also included in ES5. We will take a look at this two low level methods later in the series when we examine the mysteries of object internals. Let’s go back to object initializers!

Method Overloading

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

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

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

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

Instead use any of the patterns that you saw in the previous article to achieve method overloading, for instance:

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

Which provides a naive yet functioning implementation of method overloading:

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

Creating Objects With Factories

Since creating one-off objects through object initializers can be tedious, particularly whenever you need more than one object of the same type, we often use factories3 to encapsulate object creation:

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

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

1
2
3
4
5
6
7
var tinySpider = monster('tiny spider', /* hp */ 1);
tinySpider.movesTo(1,1);
// => tiny spider moves to (1,1)

var giantSpider = monster('giant spider', /* hp */ 200);
giantSpider.movesTo(10,10);
// => 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 the OOP section where we will see an alternative to classical inheritance in the shape of object composition via mixins, but now 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 when 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 so that 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function stealthyMonster(type, hp){
  var position = {x: 0, y: 0};

  return {
    type: type,
    hp: hp || 10,
    toString: function(){return 'stealthy ' + this.type;},
    movesTo: function (x, y){
      console.log(this + ' moves stealthily to (' + x + ',' + y + ')');
      // this function closes over (or encloses) the position variable
      // position is NOT part of the object itself, it's a free variable
      // that's why you cannot access it via this.position
      position.x = x;
      position.y = y;
    }
  };
}

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
2
3
4
5
6
7
var darkSpider = stealthyMonster('dark spider');
console.log(darkSpider.position)
// now position is completely private
// => undefined

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

sugaryCritter.movesTo(10, 10);
// => sugary ES6 critter moves to (10, 10)

With shorthand notation you can skip the function keyword and collapse the parameters of a function directly after its name. Additionally, when we create an object using existing variables we can take advantage of the same shorthand syntax for properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function sugaryStealthyMonster(type, hp = 10){
  let position = {x: 0, y: 0};

  return {
    // with property shorthand we avoid the need to repeat 
    // the name of the variable twice (type: type)
    type,
    hp,
    toString(){return `stealthy ${this.type}`;},
    movesTo(x, y){
      console.log(`${this} moves stealthily to (${x},${y})`);
      position.x = x;
      position.y = y;
    }
  };
}

let sugaryOoze = sugaryStealthyMonster('sugary Ooze', /*hp*/ 500);
sugaryOoze.movesTo(10, 10);
// => 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 on 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 in the next section). Take a look at this:

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

In this example you can 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. If you inspect the object itself, you can see how each property has been intrepreted as a string:

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

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

1
2
console.log(crazyMonkey[theArrow]);
// => "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 brings us to ES6 symbols and how to simulate data privacy in JavaScript with them.

ES6 Symbols and Data Privacy

ES6 Symbols offer us a new approach to data privacy in addition to using closures. Symbols are a new type in JavaScript conceived to represent constants and be used as identifier for object properties or, as stated in the spec, the set of all non-string values that may be used as the key of an object property 4. They are immutable and can have a description associated to them.

Symbols can be created using the Symbol function:

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

You can add a description to a Symbol for easier debugging since the toString method displays the description:

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

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

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

Because 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), if you don’t expose that Symbol to the outer world you have provided yourself with data privacy. Let’s see how symbols and data privacy via symbols work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function flyingMonster(type, hp = 10){
  let position = Symbol('position');

  return {
    [position]: {x: 0, y: 0},
    type,
    hp,
    toString(){return `stealthy ${this.type}`;},
    movesTo(x, y){
      console.log(`${this} flies like the wind from (${this[position].x}, ${this[position].y}) to (${x},${y})`);
      this[position].x = x;
      this[position].y = y;
    }
  };
}

let pterodactyl = flyingMonster('pterodactyl');
pterodactyl.movesTo(10,10);
// => stealthy pterodactyl flies like the wind from (0,0) to (10,10)

Since we don’t have a reference to the symbol because it is scoped within the flyingMonster function, we cannot access the position property:

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

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

1
2
console.log(pterodactyl[Symbol('position')]);
// => 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
2
3
4
5
6
7
8
9
var symbolsUsedInObject = Object.getOwnPropertySymbols(pterodactyl);
var position = symbolsUsedInObject[0];
console.log(position.toString());
// => Symbol(position)
// Got ya!

console.log(pterodactyl[position]);
// => {x: 10, y: 10}
// ups!

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

Concluding

In this article 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 learnt about the small improvements that ES6 brings to object initializers with the shorthand notation for both methods and properties. We wrapped the article with a review of the new ES6 Symbol type and its usage for attaining a soft version of data privacy.

Do Some Research!

Buy the Book!

If you liked this article take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

Have an awesome day! :)

More Articles in These Series


  1. As long as it is not frozen via Object.freeze, which makes an object immutable to all effects and purposes.

  2. I say mostly because if you have a this keyword within a method and within a callback function then you are screwed (which I dare say is pretty common). But worry not! You’ll learn everything there is to learn about this in the next article.

  3. or the new operator that we’ll see when we get to the OOP section of the series

  4. that’s from the one and only JavaScript specification ECMA-262 (http://bit.ly/es6-spec-symbols)

Comments