In programming, there is a technique for writing code called object-oriented programming. It's a methodology for code abstraction and organization where the code written embodies the traits of real-world objects (like a car, a house or even a person) and their related features. Although each programming languages differ in the implementation of OOP, the concept remains the same.
Encapsulation
In object-oriented programming, programs are divided into pieces, and each piece is responsible for managing its state. The way a piece of program works is local and encapsulated to that piece. This is known as encapsulation.
Different pieces of the program interact with each other through interfaces --- functions or bindings that provide interaction, and useful functionalities at an abstract level, hiding their precise implementation. Such programs are modelled after objects, again, like a car, a house or a person.
An object consists of properties (attributes and methods), properties that are part of an object, and only work within that object are private. Others, that part of the object, but interact with outside code are called public.
In JavaScript, there is no distinct way to identify private, and public properties, and to prevent outside code from accessing the private properties. One common way is to describe properties that are private or public in the documentation and comments. Another way is to use an underscore (_) at the start of property names to indicate that they are private.
Method
Methods are property names in an object that hold function values.
For instance, a simple method:
const pet = {};
pet.bark = (bark) => {
console.log(`My dog says '${bark}'`);
}
pet.bark("Woof") // → My dog says 'woof'
Methods are called on object to perform certain tasks. When a function is invoked as a method, the binding this
points to the object that the method was called on.
For instance:
function speak(line) {
console.log(`The ${this.position} person says '${line}'`);
}
const firstHomie = {position: "first", speak};
const secondHomie = {position: "second", speak};
firstHomie.speak("Yo man, wats happenin?");
// → The first person says 'Yo man, wats happenin?'
secondHomie.speak("All good dawg!");
// → The second person says 'All good dawg!'
The this
can be called using a function method called call
. The call
method takes the value this
as its first argument, other arguments are treated as normal parameters.
speak.call(secondHomie, "Good to know."); // → The second person says 'Good to know.'
NOTE:
In JavaScript, the "this" always binds or points to the object, and can be used to reference the object.
Regular function defined with the function
keyword can't refer to the this
of a wrapping scope. such a function can only use its own this
binding. Arrow functions don't work in the same way. An arrow function is able to access the this
binding of the scope around it.
for instance, reference this
from inside a local function:
/* Using normal function */
function normalize () { // → wrapping scope
console.log(this.coords.map(function(n){
n / this.length
}));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → Undefinded values [ undefined, undefined, undefined ]
/** Using arrow function **/
function normalize () {
console.log(this.coords.map(n => n / this.length));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → [0, 0.4, 0.6]
Prototypes
In JavaScript, most objecst can inherit properties from a parent object or a prototype. When an object is requested to access a property it does not have, it'll look into its prototype, if that prototype doesn't have it, then the prototype's prototype will be checked and so till it gets to the ancestor prototype.
let emptyObject = {};
console.log(emptyObject.toString());
// → [object Object]
In the above, the object is able to access a method that is not part of its property, but part of a fallback prototype that most object inherit from -- the ancestral object prototype, Object.protoptype
.
You can check the prototype of an object using the object constructor's getProtoypeOf
method:
//...
console.log(Object.getPrototypeOf(emptyobject));
// → {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ,…}
The getProtoypeOf
method returns the ancestral prototype properties. Object.prototype
provides a few default methods, like toString()
, that shows up in all object.
Some JavaScript object don't inherit from Object.prototype
as their prototype, but from another object that provides a set of default values. Date inherit from Date.prototype
, arrays from Array.prototype
, and functions from Function.prototype
.
You can use the create
method from the object constructor to create an object with its unique prototype.
let protoMouse = {
speak(line) {
console.log(`The ${this.type}Mouse says ${line}`);
}
}
let dangerMouse = Object.create(protoMouse);
dangerMouse.type = "DANGER";
dangermouse.speak("Pooww!")
// → The DANGER mouse says Poow!
From the code above, the dangerMouse
object has a default property of speak
from its fallback object protoMouse
and a property of type
that applies only to itself. The protoMouse
object can be used as a container for all mice. You could have "ninja mouse", "nerdy mouse", "tech mouse" and so on. Each mouse object can implement its property but all share the same prototype, protoMouse
.
JavaScript Classes
Before the class template was introduced in JavaScript, the language used a prototype in implementing OOP classes.
A class is an OOP concept for defining the properties (methods and attributes) of an object in a structured manner. It's the blueprint of an object. Objects derived classes are an instance of that class.
Prototypes can be used for defining properties from which all instances of a class can inherit (share properties). Dissimilar or different properties per instance, like type, have to be defined in the object.
To create an instance of a class, you first create an object that inherits from the proper prototype. The object has to have the properties that instances of that class are supposed to have. That is what constructor functions do.
//...
function makeMouse(type) {
let mouse = Object.create(protoMouse);
mouse.type = type;
return mouse;
}
// Instance of 'makeMouse' class
let ninjaMouse = makeMouse("Ninja");
ninjaMouse.speak("Haiiyahh!");
// → The NinjaMouse says Haiiyahh!
The code above is one way of doing it, but there is an easier way: using the new
keyword.
When a function is prepended with the new
keyword, it creates and automatically returns an empty object that is bounded to the this
keyword. The object has a prototype property derived from Object.prototype
.
Let's create a different class this time:
function Person(first, last, age, eye) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eye;
}
// All instances will share this property
Person.prototype.speak = functin(line) {
console.log(`${this.firstName} says '${line}'`);
}
// An instance of the 'Person' class
let teacher = new Person("Romeo", "Peter", 22, "black");
let student = new Person("Jane", "Doe", 25, "brown");
console.log(teacher.name); // → Romeo
console.log(student.name); // → Jane
teacher.speak("hello world!"); // → Romeo says 'hello world!'
student.speak("hello!"); // → Jane says 'hello!
NOTE:
It is considered good practice to name constructor functions with an upper-case first letter.
Because constructors are functions, the actual prototype of the function is Function.prototype.
Class template
A class template is a constructor function with a prototype property. It allows for a much simpler way of writing classes in JavaScript. The feature is part of the ES2015 update to JavaScript.
class Person {
constructor(first, last, age, eye) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eye;
}
speak(line) {
console.log(`${this.firstName} says '${line}'`);
}
}
// Instances of 'Person' class
let teacher = new Person("Romeo", "Peter", 22, "black");
teacher.speak("hello world!"); // → Romeo says 'hello world!'
The class
keyword is used to start or declare the class, it's followed by two curly braces {}
. you can declare any number of method within the class that'll be part of the prototype, but the constructor is special and must come first. The constructor method is a construction function that will be bounded to the class name.
NOTE:
It is considered good practice to name class template with an upper-case first letter.
You can compare the previous class declaration that used a function to this current, one and you'll see that they're similar. Using the class template is a simpler way to write read classes.
Although class template can only hold methods (function values) for now, it is a better approach to using classes. A future update to JavaScript might allow for other values to be stored within class templates.
Polymorphism
Polymorphism means "many forms."
In computer programming, polymorphism refers to data or object that can be used or processed in more than one form. This is a key part of OOP because it allows instances of a class to be any form as long as the expected interfaces or data types are provided.
For instance, we described what form a person can by declaring the Person
class. Consequently, a person can be a Father, Mother, Daughter or Son.
// ...
// Instances of 'Person' in more than one form
let father = new Person("John", "Doe", 30, "black");
let Mother = new Person("Jane", "Doe", 25, "brown");
let daughter new Person("Gill", "Doe", 3, "black");
let son = new Person("Jack", "Doe", 3, "brown");
Another example, although more technical, is the JavaScript String()
method that converts a value to a string. It is a polymorphic code that expects a certain interface to function as it should -- convert a value to a string.
When a String()
method is called on an object, it'll calls the toString()
method on that object and then converts it to a string. the method (String()
) expect the object to have the toString()
method as an interface.
It's possible to overwrite the toString()
method in the prototype. Let's do that by creating the string to be returned by the String()
method.
// ...
Person.prototype.toString = function() {
return `Teacher's name is ${this.firstName}.`;
}
console.log(String(teacher)); // → Teacher's name is Romeo.
Polymorphic code can work with data values of different forms, as long they provide the data types or interfaces required.
Getters, Setters and Static
Getters and setters allow you to read and write to an object expression or class declaration. These are properties that have hidden method calls.
You define a getter by using the keyword get
in front of a method in an object expression or class declaration.
For instance, a class for getting varying sizes:
// Object expression
let varyingSize = {
get size() {
return Math.floor(Math.random() * 100);
}
}
console.log(varySize.size) // → 12
//-----------------------------------
// Class declaration
class VarifyingSize {
get size() {
return Math.floor(Math.random() * 100);
}
}
let size = new verifyingSize();
console.log(size.size); // → 61
from the code above, when you read the object's or class' size property, it calls the associated method. Similarly, you can write into an object or class by defining a setter.
For instance, a temperature class that sets to Fahrenheit:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 1.8 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) / 1.8;
}
}
let temp = new Temperature(40);
// Get
console.log(temp.farenheit); // → 104
// Set
temp.fahrenheit = 86;
console.log(temp.celcius) // → 30
Static methods, when set, are attached (stored implicitly) to the class constructor and don't have access to the class instances. That means they are methods set for a class and not for the instance of that class. Such a method can be used to provide additional ways of creating instances. You define a static method by using the static
keyword in front of the method.
class Temperature {
//...
// Store on the class constructore
static fromFahrenheit(value) {
return new Tempareture((value 32) / 1.8);
}
}
// Create temperature using degrees Fahrenheit
Tempareture.fromFahrenheit(100);
// → Temperature {celsius: 37.77777777777778}
Inheritance
Inheritance in OOP is when a class extends another class.
When you inherit from a class, you create new functionalities and features on top of the existing one.
// Parent or super class
class Animal {
constrcutor(name) {
this.name = name;
this.speed = 0;
}
run(speed) {
this.speed = speed;
console.log(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
console.log(`${this.name} stands still.`);
}
}
//...
The above code is a generic class for animals. A generic animal like a horse can run and stop. To create a horse object, the object would have to extend the Animal class. The extends
keyword is used to achieve that. The keyword extends
tells the class not to directly derive from the default Object.prototype
but from a class
class Horse extends Animal {
hide () {
alert(`${this.name} hides!`);
}
}
let horse = new Horse("Black Stallion");
horse.run(120) // → Black Stallion runs with speed 120.
horse.hide("neigh") // → Black stands hides!.
horse.stop() // → Black stands still.
JavaScript's prototype makes it possible to derived properties from one class to another. The top class knows as the Parent class, shares its properties with the bottom, known as the child class. The child class can define its properties, like a method.
NOTE:
Any expression is allowed after
extends
Class syntax allows to specify not just a class, but any expression after
extends
.For instance, a function call that generates the parent class:
function f(phrase) { return class { sayHi() { alert(phrase); } }; } class User extends f("Hello") {} new User().sayHi(); // Hello
Here
class User
inherits from the result off("Hello")
.That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them.
Source: JavascriptInfo
By default, all methods not specified in the child class are inherited from the parent class. For instance, the stop()
method in the previous example is derived from the parent class. If the same method is specified in the child class it'll overwrite the parent's method. Properties in the child class should only extend or build on top of the parent class. To avoid overwriting methods, a child class should call parent class properties (methods and constructor) with the super
keyword.
For instance, the horse will autohide when stopped.
class Horse extends Animal {
hide () {
alert(`${this.name} hides!`);
}
stop() { // Child class method. Does not overwrite parent
super.stop(); // Call parent 'stop()' method
this.hide(); // hide
}
}
The horse class above has a stop method that calls the parent's stop method underneath in the process.
Classes that extend another class with no constructor method automatically generate an "empty" constructor. If no explicit constructor method is written, it calls the parent constructor and passes all the arguments in.
class Horse extends Animal {
// generated for extending classes without own constructors
constructor(...args) {
super(...args)
}
}
To add a custom constructor to child classes, the constructor must call super()
before using this
. This is because the derived constructor can't access the object for this
if the parent constructor is not called first.
class Horse extends Animal {
constructor(name, sound) {
super(name);
this.sound = sound;
}
//...
}
sound() {
console.log(`The ${this.name} ${this.sound}'s.`)
}
let horse = new Horse("Black Stallion", "neigh")
console.log(horse.name) // → Black Stallion
horse.sound() // → The Black Stallion neigh's.
Inheritance is a fundamental part of object-oriented programming. It allows building on top of existing data. However, unlike encapsulation and polymorphism that allow for code separation into pieces, thus reducing the overall entanglement, inheritance link and tie code together, thus increasing the tangle. When inheriting from class, knowing when to use it is just as important as how to use it.
Summary
A recap of what we've covered so far.
Object-oriented programming is a methodology for code organization by representing data objects.
Encapsulation allows pieces of code to manage their state, and interact with other code through an interface.
In JavaScript, properties that operate within an object are private while properties that interact with outside code are public.
There are two ways to distinguish public and private properties:
- Specify in documentation public and private properties.
- Use underscore _ in front of the property that is private.
Prototypes are fallback objects that an object can inherit from. Most built-in objects inherit from the ancestral Object.prototype
object.
Classes are the blueprint for an object. Objects derived from classes are instances of the class.
The class template was introduced in ES2015 and is a constructor function with a prototype property. It allows for a simpler way of describing classes
Polymorphism allows for objects to be used in more than one way.
Getters & setters allow to read and write to an object or class declaration.
Inheritance allows for an extension of classes. The class that extends to another class is known as the child class and the class that is extended from is known as the parent class.