Good day guys, in this article we are going to be discussing about Objects in JavaScript, how OOP (object oriented programming) is implemented in JavaScript. The things we will be discussing about includes:
- Creating Objects
- Classes
- encapsulation
- Inheritance
- Polymorphism
- methods of the Object data type
What is OOP?
To keep it simple Object Oriented Programming is a way of programming that involves modeling our code around real life objects. Say we are working on a code about heroes, we can define a template (class) that specifies what a hero is and then we can create the actual heroes from it, and each hero would make a reference to hero object that is based on the template we provided.
We can write another article about OOP but that is not in the scope of this article. We just want to discuss about how it is implemented in JavaScript, if you are curious about OOP and you want to read more about it i advice that you check this article.
Creating Objects In JavaScript
An object in JavaScript is just a collection of key-value pairs, separated by comma and housed within curly braces {}
. That object can be assigned to a variable at the point of creation. The keys are used to store and identify a value inside an object, using the key or named identifier we set for that value. Each value is assigned to a named identifier by a colon for example name:sam
however they must be enclosed inside curly braces {key: value, key: value}
. The keys can store any type as a value, it can be an array or a string or a function or even another object, but you get the point? The properties on the object can then be accessed using square bracket or dot notation.
There are three ways that we can create an object in JavaScript and it includes;
- Using object literals
- Defining a constructor function.
- Using the built in Object constructor
Object Literals
We can easily create an object literal in JavaScript, the literal maybe an empty object or we can populate it with key-value pairs, we can access the properties on the object using dot notation or square bracket notation.
let's see how;
let hero = {} //empty object literal;
let myHero = {
name: 'superman',
alias: 'clark kent'
} //object literal with key-value pairs
console.log(hero) // {}
console.log(myHero) // {name : 'superman', alias: 'clark kent'}
console.log(myHero.name) //superman dot notation
console.log(myHero['alias']) // clark kent square bracket notation
You would agree with me that this is a simple way of creating a plain object,
Built in Object Constructor
There is a built in constructor function that we can use to create an object
let hero = new Object() //Built in Object constructor
We can tack on properties to the object after we have created it.
hero.name = 'superman'
hero.save = function (person) {
return `${this.name} saved ${person}`
}
We can then use those properties later
console.log(hero.name) // superman
console.log(hero.save('louis lane')) //superman saved louis lane
We can create a new object based on a previous object by passing the previous object into the constructor function;
let hero2 = new Object(hero)
console.log(hero2.name) // superman
If we pass in a previous object to the function, it will copy the properties of the previous object to the new object, we can also use Object.create() method and it works similarly to the constructor method;
let hero3 = Object.create(hero)
console.log(hero3.name) //superman
Defining a constructor function
We can define a custom constructor function that will act as a class to subsequent objects created from it. We can verify thus using the __proto__
function which is attached to all objects to show you the object they inherit from. A custom constructor function is just a function that can be used to create a custom object. It's __proto__
property will point to that object, when we use the custom constructor function with the new keyword
function Hero (name) {
this.name = name;
this.save = function (person) {
return `${this.name} saved ${person}`
}
}
let superman = new Hero('superman')
console.log(superman.save('louis lane'))
// superman saved louis lane
console.log(superman.__proto__)
// Hero {}
Classes
ES6 specification of JavaScript provided a cleaner way of handling custom constructor functions for JavaScript Objects by introducing the class keyword. It handled object creation in a traditional manner that can be compared to what is done in some object oriented languages.
class Hero {}
And we have created a simple class, this is just an abstraction of the previous method of handling class creation using custom constructor function. We can add properties and methods inside the object, all objects created using this way can define a constructor function inside them that must be named constructor.
class Hero {
name;
constructor(name){
this.name = name
}
save (person) {
return `${this.name} saved ${person}`
}
}
const superman = new Hero('superman')
console.log(superman.save('louis lane'))
// superman saved louis lane
console.log(superman.__proto__)
Observe the way methods are added to the objects. we just give it a name and then parenthesis and curly braces, if we tried to get the __proto__
property on the object it would point to Hero {}.
Getter and Setters
When we use the class keyword to define a custom constructor function we can define a getter and setter function that can be used to get and set a property of the object.
class Hero {
constructor(name){
this._name = name
}
save (person) {
return `${this._name} saved ${person}`
}
get (){
return this._name
}
set (newName){
this._name = newName
}
}
const superman = new Hero('superman')
console.log(superman.save('louis lane'))
// superman saved louis lane
console.log(superman._name)
And it get and set the appropriate variable. But the key must begin with an underscore character
Encapsulation
It is required that all properties of an object should be housed within the object itself, this is the basis of encapsulation. Encapsulation is achieved in JavaScript when we define a class or a custom constructor function. All the properties and methods that the object will possess is stored inside the class, when the class is instantiated (creating an instance of a class, which is the actual object we are creating) the properties specified on the class will be housed within the object and only that object can have access to that property. This is why we use the this keyword when we are defining the class or the custom constructor function, it binds the current instance of an object to it, so when we say this.name = new name and we later tried to set the value, JavaScript will take that particular object's name, or if you prefer that particular instance of that class and replace this wherever the this keyword is used inside it.
Inheritance
Inheritance in JavaScript is prototypical, by that i mean that we say that one class inherits from another class by saying that the sub class is a prototype of the base class, inheritance can be achieved with custom constructor functions and classes. Let's see how we can achieve this with custom constructor the prototype property.
function Hero(name, alias){
this.name = name;
this.alias = alias
} //base class
//we can add properties to the prototype property of our Hero class
Hero.prototype.savePerson = function (person) {
console.log(`${this.name} saved ${person}`)
}
// ceating a class that will inherit from the Hero class
function DCHero(name, alias){
//we don't need to manually set the properties like in the Hero class
Hero.call(this, name, alias) // we use the Hero constructor
}
//finally we set the prototype property of DCHero to be same with Hero class
DCHero.prototype = Object.create(Hero.prototype)
// creating an instance of a hero
const superman = new DCHero('superman', 'clark kent')
superman.save('louis lane')
console.log(DCHero.prototype)
When we set the prototype of the class to be equal to another class it inherits the properties defined on the prototype property of the base class we are inheriting from. If we want a method or property specified on the class to be defined on inherited classes we can add the property or method to the prototype property of the base object. Observe how we use Hero.call(this, name, alias), what we are simply doing here is calling the Hero constructor and using it to create an instance of a DCHero since they are accepting the same arguments. What if we wanted to modify the DCHero class slightly so it accepts a third parameter when we are instantiating it? Maybe a movie the hero featured in?
function Hero(name, alias){
this.name = name;
this.alias = alias
} //base class
//we can add properties to the prototype property of our Hero class
Hero.prototype.savePerson = function (person) {
console.log(`${this.name} saved ${person}`)
}
// we added a third argument to the constructor function
function DCHero(name, alias, movie){
Hero.call(this, name, alias) // we use the Hero constructor
this.movie = movie
}
// inheritance is still the same
DCHero.prototype = Object.create(Hero.prototype)
const superman = new DCHero('superaman', 'clark kent', 'man of steel');
superman.save('louis lane')
Class Based Inheritance
We can inherit from another class when using ES6 classes by using the extends
keyword. The sub class extends the base class. The syntax is shorter and less verbose than the custom constructor method. Let's see how we can achieve this.
class Hero { //base class
constructor(name, alias){
this.name = name;
this.alias = alias
}
save (person) {
console.log(`${this.name} saved ${person}`)
}
}
class DCHero extends Hero{ // sub class with extra argument in constructor function,
//it extends Hero, hence it is inheriting from it.
constructor(name, alias, movie){
super(name, alias)
this.movie = movie
}
}
const batman = new DCHero('batman', 'bruce wayne', 'superman vs batman');
batman.save('robinhood')
console.log(DCHero.prototype) // Hero
Polymorphism
Polymorphism is a concept in OOP that specifies that sub classes can have different implementation of the same method. A method or property can be defined on the Base class and in the sub class we specify a different logic inside that function, or we set that property to be equal to something else, let's see a typical example.
class Hero { // base Hero class other classes will inherit from
constructor(name, alias){
this.name = name;
this.alias = alias
}
universe;
save (person){
console.log(`${this.name} saved ${person}`)
}
}
// sub Marvel Hero class to inherit from Hero
class MarvelHero extends Hero {
constructor(name, alias){
super(name, alias)
}
universe= 'marvel universe'
save (person){
console.log(`${person} saved by ${this.name}`)
}
}
// sub DC Hero class to also inherit from Hero
class DCHero extends Hero {
constructor(name, alias){
super(name, alias)
}
universe= 'dc universe'
save() {
console.log(`${this.name} has saved the day`)
}
}
const sipderman = new MarvelHero('spiderman', 'peter parker')
const superman = new DCHero('superman', 'clark kent')
console.log(spiderman.universe) // marvel universe
console.log(superman.universe) // dc universe
sipderman.save('mary jane') // louis lane was saved by spiderman
superman.save() // superman has saved the day
The DCHero and the Marvel both inherit and implements the save function, however each sub class has it's own definition or implementation of the method, and that's how polymorphism can simply be achieved in JavaScript.
Methods of the Object data type
There are some built in functions that we can use on objects to make working with them simpler, let's look at a few of them.
Object.keys
This method simply return an array of the keys of an object, take note, only the keys and not their values, their exists a different method for that.
const hero = {name: 'thor', universse: 'marverl'}
const heroKeys = Object.keys(hero)
console.log(heroKeys) // [name, universe]
Object.values
This method returns an array of the values of the keys of an object, it is similar to Object.keys, however it returns the values of the different keys on the object as an array
const hero = {name: 'cyborg', universe: 'dc'}
const heroKeys = Object.values(hero);
console.log(heroKeys)
Object.freeze
This method freezes up an object and prevents any modification of the keys specified on the object.
const hero = {name: 'ironman', alias: 'tony stark'}
Object.freeze(hero)
hero.name = 'something else' //not possible
Object.isFrozen
This is used to check if an object if froozen or not, if the object is frozen, it returns true, if it is not froozen,it returns false.
const hero = {name: 'ironman', alias: 'tony stark'}
Object.freeze(hero)
Object.isFrozen(hero)? console.log(true) : console.log(false) //true
Object.assign
This is simply a way of assigning the key-value pairs on one object to another object
const powers = {power: ['speed', 'strength', 'stamina']}
const hero = {name: 'superman'}
Object.assign(hero, powers)
console.log(hero) // { name: 'superman', power: ['speed', 'strength', 'stamina']}
Object.entries
This method creates an array containing the different arrays that maps to each key-value pair on the object. It is an arrays of arrays, each key-value pair is mapped to an array, the first value is the key while the next is it's value.
const DCHero = {
name: 'superman',
world: 'krypto',
weakness: ['magic', 'cryptonite'],
powers: ['speed', 'strength', 'heat vision']
}
const arr = Object.entries(DCHero)
console.log(arr) /* [
[ 'name', 'superman' ],
[ 'world', 'krypto' ],
[ 'weakness', [ 'magic', 'cryptonite' ] ],
[ 'powers', [ 'speed', 'strength', 'heat vision' ] ]
] */
This will be all for now, do have a nice day and until next post.