Practical Object-Oriented JavaScript

icheka

Icheka Ozuru

Posted on February 15, 2021

Practical Object-Oriented JavaScript

Object-Oriented: What Does It Mean?

As human beings, our lives depend on objects within our immediate and extended environments. We see, hear, feel, and manipulate various objects as we go about our daily business. The bunch of keys (and each individual key), the door knob, the bus, the iPhone, are all objects that we interact with in diverse and often complicated ways. Our perception of the world that surrounds us is based entirely on our experiences and memories of past interactions with objects. Interacting with objects requires us to assess them using metrics that are either distinct and measurable, or intuitive and immeasurable. For example, we often describe objects in terms of their size, their weight, their color and their value. In fact, these four qualities are the most intrinsic we can find for most objects.

Object-Oriented Programming recognizes the place of objects as central to our experience of life as humans and extends this into our programming. Object-Oriented (or Orientation Toward Objects) reminds the programmer that in OOP code, objects (and their attributes) are superlative. Prior to the development of Object-Oriented Programming languages and practices, programmers could only struggle to express physical structures (like rocks) and their attributes (like the color grey) using "primitive" data structures like the linked list and the array. This practice has proven to be quite inadequate for providing the kind of flexibility and seamlessness that today's applications demand. For example, it would be quite difficult to express a soccer player in a video game as an array or a hash table.

// soccer player as an array
// [name, nationality, occupation]
const Messi = ['Lionel Messi', 'Argentine', 'Soccer player']
Enter fullscreen mode Exit fullscreen mode

Arrays are one on the most handy data structures we can use, but they are just unwieldy for this kind of description. To describe Lionel Messi better, in a way that we can easily manipulate, we need another data structure: objects.

JavaScript Objects

Objects in JavaScript are often more flexible to work with:

// soccer player as an object
const Messi = {
    'name': 'Lionel Messi',
    'nationality': 'Argentine',
    'occupation': 'Soccer player'
}
Enter fullscreen mode Exit fullscreen mode

In JavaScript, objects are often created by simply declaring a variable (using the let, const or var keywords) and assigning data to it using the object curly brace syntax.

Curly brace syntax: key-value pairs of data, separated from each other by single commas, and enclosed within a pair of opening and closing curly braces.

/*
{
    key: value
}
*/
... {
    'name': 'Lionel Messi',
    ...
}
Enter fullscreen mode Exit fullscreen mode

Objects like this are handy because we can easily refer to their attributes using their keys, like so:

// two methods of referring to object attributes
// 1. using the 'dot' syntax
console.log(Messi.name);
// prints "Lionel Messi"

// 2. using square brackets
console.log(Messi['name'])
// prints "Lionel Messi"
Enter fullscreen mode Exit fullscreen mode

Most people prefer to use the 'dot' syntax to refer to traverse their objects.

Creating Re-usable Objects

We have our 'Messi' object and we have defined a name, nationality and occupation for our object. We can easily extend the object's properties by adding attributes as key: value pairs. Like so:

// Messi object, extended
const Messi = {
    'name': 'Lionel Messi',
    'nationality': 'Argentine',
    'occupation': 'Soccer player',
    'team': 'FC Barcelona',
    'age': 33
}

console.log(Messi.age, Messi.team)
// prints 33 "FC Barcelona"
Enter fullscreen mode Exit fullscreen mode

But what if we wanted to create more soccer players for our video game? Would we define and initialize objects like this for the 11-player FC Barcelona team? Plus another 11 for Manchester United's? This might work, but what if we need to extend our player objects? For example, if we wanted to add a 'height' property to our players? Would we make this change in 22 places? Or in 990, if we have 90 teams in our video game?

Luckily for us, the variable declaration method isn't all that we have to work with. More robust options exist, of which about the most robust is to create a prototype so that we can simply copy or clone similar objects from one source. My favorite way of doing this is using the class keyword.

The class keyword is special in a lot of languages, particularly Object-Oriented ones. It is widely used to declare a Class, or a blueprint or schema for cloning objects. The class keyword is used across several popular languages, such as Python, C++, Java, C#, PHP and CSS. It almost always means: "here comes a blueprint for a specific kind of object". After creating our classes, we can easily derive as many objects as we need from them. Each object is a child that will inherit all of its parent's attributes/properties.

// create a class called Player
class Player {
    setName(name) {
      this.name = name
    }
}

// clone three objects from it
let player_1 = new Player();
let player_2 = new Player();
let player_3 = new Player();

// set player_1's name
player_1.setName('Lionel Messi')
// print player_1's name
console.log(player_1.name)
// prints "Lionel Messi"

// set player_2's name
player_2.setName('Diego Maradona')
// print player_2's name
console.log(player_2.name)
// prints "Diego Maradona"
Enter fullscreen mode Exit fullscreen mode

We started by creating a class, using the class keyword:

class Player {
}
Enter fullscreen mode Exit fullscreen mode

Next, we added a function (also called a member function) for setting the Player object's name. Note that calling console.log(player_1.name) before setting the name will print undefined. In JavaScript, member functions of prototypes (i.e blueprints) defined using the class keyword are not defined with the function keyword:

class Player {
    // right
    setName(name) {
        this.name = name
    }

    // wrong
    function setName(name) {
        this.name = name
    }
}
Enter fullscreen mode Exit fullscreen mode

The setName member function sets the name property of the object it is called from.

...
    this.name = name
...
Enter fullscreen mode Exit fullscreen mode

Finally, we print the name property of our objects:

...
console.log(person_2.name)
...
Enter fullscreen mode Exit fullscreen mode

Attributes of Objects: Properties and Member Functions

Objects in programming, in general, and in JavaScript, in particular, have attributes that are inherent and intrinsic to their nature, first as objects, and second as specific objects. Specificity refers to objects that are derived from the same prototype (or class). These attributes also exist in the real world. They are not some abstract idea that is far removed from the realm of the average developer. These attributes are divided into two groups:

  1. Object properties
  2. Object member functions

Understanding these attributes is easy: a soccer player like Lionel Messi has a name, measurable weight, height, complexion and favorite color. These are all object properties.

class Player {
    setProps(name, age, weight, height) {
        this.name = name
        this.age = age
        this.weight = weight
        this.height = height    
    }
}

let Messi = new Player()
Messi.setProps("Lionel Messi", 33, "200Ibs", "1.7m")

console.log(Messi)
/*
prints --
Object {
  age: 33,
  height: "1.7m",
  name: "Lionel Messi",
  weight: "200Ibs"
}
*/
Enter fullscreen mode Exit fullscreen mode

In real life, Lionel Messi knows how to sprint, tackle, kick, jump, and shoot. These are member functions.

class Player {
    setProps(name, age, weight, height) {
        this.name = name
        this.age = age
        this.weight = weight
        this.height = height    
    }

    tackle() {
        console.log(this.name + " executed a tackle!")
    }

    sprint() {
        console.log(this.name + " is running!")
    }

    shoot() {
        console.log(this.name + " kicked the ball really hard this time!")
    }
}

let Messi = new Player()
Messi.setProps("Lionel Messi", 33, "200Ibs", "1.7m")
Messi.sprint()
Messi.tackle()
Messi.shoot()
Enter fullscreen mode Exit fullscreen mode

Performing Time-Of-Instantiation Tasks With Constructors

You'll often find that there are certain tasks that you would like your object to perform as soon as it is created (properly called instantiation). Such tasks may include starting an event loop, making an API call, or simply setting a few key properties (e.g name, age and height). In our code above, we can notice that Messi.setProps() needs to be called as soon as possible after the Messi object is instantiated. To 'instatiate' means to create a new object from a prototype using the new keyword, just as we have been doing. The name, age, weight and height properties need to be set before any call to the action member functions (sprint, shoot, tackle) may be called. Calling Messi.tackle() before Messi.setProps(), for example, will result in a slight error since the name property of the object has not been initialized yet. This way, it is quite easy for a programmer, even a first-rate one, to make this mistake and attempt to have the object tackle before it is given a name.

class Player {
    setProps(name, age, weight, height) {
        this.name = name
        this.age = age
        this.weight = weight
        this.height = height    
    }

    tackle() {
        console.log(this.name + " executed a tackle!")
    }
}

let Messi = new Player()
// 1
Messi.tackle()
// prints "undefined executed a tackle"

Messi.setProps("Lionel Messi", 33, "200Ibs", "1.7m")

// 2
Messi.tackle()
// prints "Lionel Messi executed a tackle"
Enter fullscreen mode Exit fullscreen mode

The task of initializing key properties is usually handled by a special kind of function called a constructor. The constructor is a special function that the programming language calls as soon as the object is instantiated. This nice feature allows the programmer to have his application handle important tasks during the object instantiation process.

In our case, we want our player objects to have a name, age, weight and height, and we would like to set up all of these as soon as the object is instantiated.

In JavaScript, the special constructor function is called simply constructor. Using the special constructor function, we can create Player objects and assign unique names, ages, weights and heights like so:

class Player {
    constructor(name, age, weight, height) {
        this.name = name
        this.age = age
        this.weight = weight
        this.height = height    
    }

    tackle() {
        console.log(this.name + " executed a tackle!")
    }
}

// create a new Player object and assign properties at instantiation-time
let Messi = new Player("Lionel Messi", 33, "200Ibs", "1.7m")

console.log(Messi.name)
// prints "Lionel Messi"
Messi.tackle()
// prints "Lionel Messi executed a tackle!"
Enter fullscreen mode Exit fullscreen mode

When I work, I usually prefer to use the constructor to initialize properties like so:

class Player {
    constructor(props) {
        Object.keys(props).forEach(prop => this[prop] = props[prop])
    }

    tackle() {
        console.log(this.name + " executed a tackle!")
    }
}

// create a new Player object and assign properties at instantiation-time
let props = {
    name: "Lionel Messi",
    age: 33,
    weight: "200Ibs",
    height: "1.7m"
}
let Messi = new Player(props)

console.log(Messi.name)
// prints "Lionel Messi"
Messi.tackle()
// prints "Lionel Messi executed a tackle!"
Enter fullscreen mode Exit fullscreen mode

This way, I can easily create several similar objects, without having to write the properties each time I create an object. I can just pass the same props object (remember, objects are key: value pairs of data: props is an object). If I need to create 5 more Messi objects:

...
let Messi2 = new Player(props)
let Messi3 = new Player(props)
let Messi4 = new Player(props)
let Messi5 = new Player(props)
let Messi6 = new Player(props)
Enter fullscreen mode Exit fullscreen mode

And if I need to effect a change in their properties later in my program, I can make that change once in the props object and all my six Messi Player objects will be updated:

...
// change the 'weight' property of the props object
props.weight = "400Ibs" // same as writing props['weight'] = "400Ibs"

...
let Messi6 = new Player(props)

console.log(Messi6.weight)
// prints "400Ibs"
Enter fullscreen mode Exit fullscreen mode

There you have it! You can go on to add more properties and methods (member functions) to your Player class, and even use it to do really nice things, like write a soccer game. You can also create other classes and objects by following the general process we used here.
I hope you enjoyed reading this as much as I did writing it.

May the Code be with us all.
Cheers.

💖 💪 🙅 🚩
icheka
Icheka Ozuru

Posted on February 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related