Object Oriented Programming In JavaScript

Factory Function

Factory functions in JavaScript are a way to create objects by encapsulating the object creation logic within a function. These functions act as factories that produce instances of objects with shared properties and methods. Here’s an explanation with code:

// Factory function example
function createPerson(name, age) {
  // Object to be created
  const person = {};

  // Properties
  person.name = name;
  person.age = age;

  // Methods
  person.greet = function() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  };

  // Return the created object
  return person;
}

// Creating objects using the factory function
const john = createPerson("John", 25);
const sarah = createPerson("Sarah", 30);

// Accessing object properties
console.log(john.name); // Output: John
console.log(sarah.age); // Output: 30

// Calling object methods
john.greet(); // Output: Hello, my name is John and I'm 25 years old.
sarah.greet(); // Output: Hello, my name is Sarah and I'm 30 years old.

In the example above, createPerson() is a factory function that takes in the name and age as parameters and creates a new object, person. The object has properties such as name and age, as well as a method greet() that logs a greeting message to the console.

By calling createPerson() and passing the appropriate arguments, we can create multiple instances of the person object, such as john and sarah. Each instance has its own set of property values, but they share the same methods defined within the factory function.

Factory functions provide a way to create objects with encapsulated creation logic and allow for the reuse of shared methods. They offer a simple and flexible approach to object creation in JavaScript.

Object.create()

Certainly! Object.create() is a method in JavaScript that allows you to create a new object with a specified prototype object. It provides a way to achieve prototypal inheritance and create objects based on an existing object as a prototype. Here’s an explanation of Object.create() with code examples:

Syntax:

Object.create(proto[, propertiesObject])

Parameters:

  • proto (required): The object to be used as the prototype of the newly created object.
  • propertiesObject (optional): An object that defines additional properties to be added to the newly created object.

Usage:

// Creating a prototype object
const prototypeObject = {
  // properties and methods...
};

// Creating a new object with prototypeObject as the prototype
const newObject = Object.create(prototypeObject);

In the example above, Object.create() is used to create a new object named newObject based on the prototypeObject. The prototypeObject serves as the prototype of newObject, meaning that newObject will inherit properties and methods from prototypeObject.

Here’s an example with additional properties:

const prototypeObject = {
  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};

const newObject = Object.create(prototypeObject, {
  name: {
    value: "John",
    writable: true,
    enumerable: true,
    configurable: true
  }
});

console.log(newObject.name); // Output: John
newObject.greet(); // Output: Hello, John!

In this example, newObject is created using Object.create(), and the prototypeObject is specified as the prototype. Additionally, the propertiesObject parameter is used to define a name property for newObject. The name property is set to “John” and has the attributes writable, enumerable, and configurable set to true.

Object.create() allows you to create objects with specific prototypes, enabling you to implement prototypal inheritance and share properties and methods among objects.

It’s important to note that Object.create() was introduced in ECMAScript 5 (ES5) and may not be supported in older browsers. However, it is widely supported in modern JavaScript environments.

Constructor Function

Certainly! In JavaScript, a constructor function is a special function that is used to create and initialize objects. It serves as a blueprint for creating multiple instances of objects with shared properties and methods. Here’s an explanation of constructor functions with code examples:

Definition and Usage:

A constructor function is created using the function keyword, and its name should be capitalized by convention. When you create an object using a constructor function with the new keyword, it creates a new instance of the object and sets the newly created object as the value of this within the constructor function.

Example:

// Constructor function example
function Person(name, age) {
  // Properties
  this.name = name;
  this.age = age;

  // Method
  this.greet = function() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  };
}

// Creating objects using the constructor function
const john = new Person("John", 25);
const sarah = new Person("Sarah", 30);

// Accessing object properties
console.log(john.name); // Output: John
console.log(sarah.age); // Output: 30

// Calling object methods
john.greet(); // Output: Hello, my name is John and I'm 25 years old.
sarah.greet(); // Output: Hello, my name is Sarah and I'm 30 years old.

In the example above, Person is a constructor function that takes in the name and age parameters. Inside the constructor function, the properties name and age are assigned to the newly created object using this. The greet() method is also defined within the constructor function using this.

To create objects using the constructor function, the new keyword is used. When new Person(“John”, 25) is called, a new instance of the Person object is created, and the name and age values are passed as arguments to initialize the object. The this inside the constructor function refers to the newly created object, allowing the properties and methods to be assigned to it.

Constructor functions provide a way to create objects with shared properties and methods. Each instance created using the constructor function has its own set of property values, but they share the same methods defined within the constructor function.

It’s important to note that constructor functions in JavaScript are a traditional way of achieving object creation and initialization. With the introduction of the class syntax in ECMAScript 2015 (ES6), you can also use class-based syntax to achieve similar results in a more structured manner.

ES6 Classes

Certainly! ES6 introduced the class syntax in JavaScript, providing a more structured and intuitive way to define and create objects. Here’s an explanation of ES6 classes with code examples:

Definition and Usage:

In ES6, you can define classes using the class keyword. A class serves as a blueprint for creating objects with shared properties and methods. Inside the class, you can define a constructor method, properties, and other methods. The new keyword is used to create instances of the class.

Example:

// ES6 class example
class Person {
  constructor(name, age) {
    // Properties
    this.name = name;
    this.age = age;
  }

  // Method
  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

// Creating objects using the class
const john = new Person("John", 25);
const sarah = new Person("Sarah", 30);

// Accessing object properties
console.log(john.name); // Output: John
console.log(sarah.age); // Output: 30

// Calling object methods
john.greet(); // Output: Hello, my name is John and I'm 25 years old.
sarah.greet(); // Output: Hello, my name is Sarah and I'm 30 years old.

In the example above, the Person class is defined using the class keyword. It has a constructor method that takes in the name and age parameters. Inside the constructor, the properties name and age are assigned to the object using this. The greet() method is also defined within the class.

To create objects using the class, the new keyword is used. When new Person(“John”, 25) is called, a new instance of the Person class is created, and the name and age values are passed as arguments to initialize the object.

ES6 classes provide a more structured and declarative way to define and create objects, making the code easier to read and maintain. The class syntax helps in organizing the code by encapsulating related properties and methods within a single class.

It’s important to note that under the hood, ES6 classes still use prototypal inheritance. The class syntax is a syntactic sugar over the prototypal-based approach, providing a more convenient and familiar syntax for creating objects.

ES6 classes also support inheritance, allowing you to create subclass relationships and extend the functionality of existing classes. To achieve inheritance, you can use the extends keyword and the super() method. However, that goes beyond the scope of this explanation.

ES6 classes are widely used in modern JavaScript development and have become the preferred way to define and create objects.

Private Class Field

Certainly! Private class fields are a feature introduced in ES2022 (also known as ES12) that allow you to define private variables and methods within a class. Private class fields are inaccessible from outside the class and can only be accessed and modified by the class itself. This provides encapsulation and information hiding, helping to protect sensitive data and implementation details. Here’s an explanation of private class fields with code examples:

Syntax:

To define a private class field, you can use the # prefix before the field name. Here’s the syntax for defining private fields and methods:

class ClassName {
  #privateField;
  
  #privateMethod() {
    // private method code
  }
}

Usage:

Let’s look at an example to understand how private class fields work:

class Person {
  #name;
  #age;

  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }

  #privateMethod() {
    console.log("This is a private method.");
  }

  getName() {
    return this.#name;
  }

  setName(newName) {
    this.#name = newName;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name} and I'm ${this.#age} years old.`);
    this.#privateMethod();
  }
}

const john = new Person("John", 25);
console.log(john.name); // Output: undefined (private field inaccessible)
console.log(john.getName()); // Output: John
john.setName("Johnny");
console.log(john.getName()); // Output: Johnny
john.greet(); // Output: Hello, my name is Johnny and I'm 25 years old. (private method called)

In the example above, the Person class has private fields #name and #age declared using the # prefix. These fields are inaccessible from outside the class. The private method #privateMethod() is also defined using the same prefix.

Within the class, the private fields can be accessed and modified directly using this.#name and this.#age. The public methods getName() and setName() are used to get and set the value of the private field #name.

When creating an instance of the Person class, you can access the public methods like getName() to retrieve the private field value. However, attempting to access the private field directly using john.name will return undefined.

The greet() method demonstrates how a private method can be called from within the class, even though it is inaccessible from outside.

Private class fields offer enhanced encapsulation and information hiding, ensuring that private data and implementation details are not accessible or modified from outside the class. They provide better control and security over the internal state of the class.

It’s worth noting that private class fields are a relatively new feature and may not be fully supported in older browsers or environments. However, they are supported in modern JavaScript environments and can be transpiled to older versions using tools like Babel.

Private vs Public

In JavaScript, the concepts of private and public refer to the visibility and accessibility of variables and methods within an object or class. These concepts help define which members of an object can be accessed from outside the object. Here’s an explanation of private and public members in JavaScript:

Public Members: Public members are accessible from outside the object or class. They can be accessed and used by other objects or parts of the code. By default, all members in JavaScript are public unless specified otherwise.

Example:

class Person {
  constructor(name) {
    this.name = name; // Public property
  }

  greet() {
    console.log(`Hello, my name is ${this.name}.`); // Public method
  }
}

const john = new Person("John");
console.log(john.name); // Accessing public property
john.greet(); // Calling public method

In the example above, the name property and greet() method are public members of the Person class. They can be accessed and used from outside the class.

Private Members: Private members are not accessible from outside the object or class. They are meant to be used internally within the object or class, providing encapsulation and information hiding. In JavaScript, private members can be achieved using various techniques such as naming conventions or, starting from ES2022 (ES12), using private class fields.

Example (Using naming convention):

class Person {
  constructor(name) {
    const privateName = name; // Private variable

    this.greet = function() {
      console.log(`Hello, my name is ${privateName}.`); // Private method
    };
  }
}

const john = new Person("John");
console.log(john.privateName); // Output: undefined (private variable inaccessible)
john.greet(); // Output: Hello, my name is John. (private method called)

In the example above, the privateName variable is declared inside the constructor function using the const keyword. It is not accessible from outside the class. The greet() method, defined within the constructor, has access to the privateName variable and can use it internally.

Starting from ES2022 (ES12), private class fields can be used to define private members directly within the class, as explained in the previous response.

Benefits: Using public and private members in JavaScript has several benefits:

  • Encapsulation: Private members hide the implementation details and protect sensitive data, allowing controlled access to internal functionality.
  • Information Hiding: Private members prevent external code from directly modifying or accessing internal state, promoting better software design and reducing bugs.
  • Code Organization: Public and private members help structure the code by distinguishing between the interface exposed to external code and the internal workings of an object or class.

It’s important to note that the concept of private members in JavaScript is based on convention or, in the case of private class fields, a language feature introduced in recent versions of JavaScript. It’s essential to consider browser compatibility and transpilation options when using private class fields, as they may not be supported in older environments.

Pillars of OOP

  1. Encapsulation
  2. Inheritance
  3. Polymorphism
  4. Abstraction

Encapsulation

In JavaScript, encapsulation in Object-Oriented Programming (OOP) refers to the bundling of data and methods within an object, hiding the internal details and implementation from the outside. It allows for data protection and controlled access to object properties and methods. While JavaScript doesn’t have built-in access modifiers like public or private, encapsulation can still be achieved using naming conventions or closures. Here’s an example of encapsulation in JavaScript:

// Encapsulation example in JavaScript

// Using naming conventions to indicate private members
function Person(name, age) {
  // Private variable
  var privateName = name;

  // Public methods
  this.getName = function() {
    return privateName;
  };

  this.setName = function(newName) {
    privateName = newName;
  };

  this.greet = function() {
    console.log(`Hello, my name is ${privateName} and I'm ${age} years old.`);
  };
}

var john = new Person("John", 25);
console.log(john.privateName); // Output: undefined (private variable inaccessible)
console.log(john.getName()); // Output: John
john.setName("Johnny");
console.log(john.getName()); // Output: Johnny
john.greet(); // Output: Hello, my name is Johnny and I'm 25 years old.

In the above example, the Person function serves as a constructor for creating Person objects. Inside the constructor, a private variable privateName is declared using the var keyword. This private variable is accessible only within the constructor function scope.

To provide controlled access to the private variable, public methods are defined using the this keyword. The getName() method allows external code to access the value of privateName, while the setName() method allows for modification. The greet() method is also a public method that can be called from outside the object.

Outside the object, the private variable privateName is inaccessible (john.privateName returns undefined). However, external code can use the public methods getName() and setName() to get and set the value of privateName, encapsulating and controlling access to the private data.

This approach demonstrates encapsulation in JavaScript using naming conventions to indicate private members. It provides a level of data protection and allows for controlled access to the internal state of objects.

It’s worth noting that this method of encapsulation using naming conventions is a convention-based approach rather than a language-enforced feature. Developers need to adhere to the convention and refrain from accessing or modifying the private members directly.

Inheritance

Certainly! In JavaScript, inheritance allows objects to inherit properties and methods from other objects. This promotes code reuse and hierarchy by enabling the creation of specialized objects based on existing ones. Inheritance is achieved through prototype chaining in JavaScript. Here’s an example to explain inheritance in JavaScript:

// Parent class
function Shape(color) {
  this.color = color;
}

// Method defined on the parent class
Shape.prototype.getColor = function() {
  return this.color;
};

// Child class inheriting from the parent class
function Circle(radius, color) {
  Shape.call(this, color);
  this.radius = radius;
}

// Inherit the prototype from the parent class
Circle.prototype = Object.create(Shape.prototype);

// Method defined on the child class
Circle.prototype.getArea = function() {
  return Math.PI * this.radius ** 2;
};

// Creating objects using the child class
var myCircle = new Circle(5, "red");

console.log(myCircle.getColor()); // Output: red (inherited from Shape)
console.log(myCircle.getArea()); // Output: 78.53981633974483 (defined on Circle)

In the example above, we have a parent class Shape that represents a basic shape and has a color property. It also defines a method getColor() using the prototype pattern.

The child class Circle is defined, which represents a circle. It calls the parent class constructor Shape.call(this, color) to inherit the color property from the Shape class. The Circle class also has its own property radius.

To establish inheritance, the Circle.prototype is set to an object created using Object.create(Shape.prototype). This ensures that the Circle class prototype chain includes the methods defined in the Shape class.

Finally, the Circle class defines its own method getArea() using the prototype pattern.

By creating an instance of the Circle class (myCircle), we can see that it inherits the getColor() method from the Shape class and also has its own getArea() method specific to the Circle class.

In JavaScript, inheritance is achieved through prototype chaining, where objects have a prototype property that refers to another object. By setting the child object’s prototype to the parent object’s prototype, you establish a chain of inheritance, allowing for method and property lookup across the prototype chain.

Note: ES6 introduced the class syntax in JavaScript, which provides a more structured and intuitive way to define classes and inheritance. The example above demonstrates the classical prototype-based inheritance approach, which is the underlying mechanism for inheritance in JavaScript.

Certainly! In ES6 (ECMAScript 2015) and later versions of JavaScript, the class syntax provides a more structured and intuitive way to define classes and inheritance. Here’s an example to explain inheritance with ES6 classes:

// Parent class
class Shape {
  constructor(color) {
    this.color = color;
  }

  getColor() {
    return this.color;
  }
}

// Child class inheriting from the parent class
class Circle extends Shape {
  constructor(radius, color) {
    super(color);
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius ** 2;
  }
}

// Creating objects using the child class
const myCircle = new Circle(5, "red");

console.log(myCircle.getColor()); // Output: red (inherited from Shape)
console.log(myCircle.getArea()); // Output: 78.53981633974483 (defined on Circle)

In the example above, the Shape class is defined using the class keyword. It has a constructor method that takes a color parameter and assigns it to the instance property this.color. The getColor() method is defined within the class, which returns the color property.

The Circle class is defined as a subclass of Shape using the extends keyword. It also has a constructor method that takes a radius and color parameter. The super(color) statement is used to call the constructor of the parent class Shape and pass the color argument to it. This ensures that the color property is set in the Shape class.

The Circle class also has its own method getArea(), which calculates and returns the area of the circle based on its radius.

By creating an instance of the Circle class (myCircle), we can see that it inherits the getColor() method from the Shape class and also has its own getArea() method specific to the Circle class.

In ES6 classes, the extends keyword is used to establish inheritance between classes. The child class automatically has access to the properties and methods defined in the parent class. The super() keyword is used to call the constructor of the parent class within the child class constructor, ensuring that the parent class is properly initialized.

ES6 classes provide a more concise and intuitive syntax for defining classes and inheritance in JavaScript, making it easier to work with object-oriented concepts.

Polymorphism

Certainly! Polymorphism in JavaScript refers to the ability of objects to respond to the same method or message in different ways. It allows objects of different types to be treated as instances of a common superclass or interface. Here’s an example to explain polymorphism in JavaScript:

// Parent class
class Animal {
  constructor(name) {
    this.name = name;
  }

  // Common method for all animals
  makeSound() {
    console.log("Generic animal sound");
  }
}

// Child classes inheriting from the parent class
class Dog extends Animal {
  makeSound() {
    console.log("Woof!");
  }
}

class Cat extends Animal {
  makeSound() {
    console.log("Meow!");
  }
}

// Polymorphic function
function animalSounds(animals) {
  animals.forEach(animal => {
    animal.makeSound();
  });
}

// Creating objects using the child classes
const dog = new Dog("Buddy");
const cat = new Cat("Whiskers");

// Calling the polymorphic function with different objects
animalSounds([dog, cat]);

In the example above, we have a parent class Animal that defines a constructor to set the name property and a makeSound() method. The makeSound() method is a common method for all animals, but the implementation differs in the child classes.

The Dog and Cat classes are child classes that inherit from the Animal class. They override the makeSound() method to provide their own implementation of the sound.

The animalSounds() function demonstrates polymorphism by accepting an array of animals. It iterates over each animal and calls the makeSound() method. Despite being different types of animals, the makeSound() method is called on each object, and the appropriate sound is emitted based on the specific implementation in each child class.

When animalSounds() is called with an array containing a Dog and a Cat object, both objects respond to the makeSound() method in their own unique way, showcasing polymorphism.

Polymorphism in JavaScript allows for writing generic code that can operate on objects of different types, as long as they share a common interface or inherit from the same superclass. It promotes flexibility, extensibility, and code reuse in object-oriented programming.

Abstraction

In JavaScript, abstraction is a concept that focuses on defining the essential characteristics and behavior of an object while hiding the unnecessary details. It allows you to create abstract classes or interfaces that define a contract for other classes to implement. Abstraction helps in creating a high-level understanding of the functionality without getting into the implementation specifics. However, JavaScript does not have native support for abstract classes or interfaces like some other languages. However, we can achieve a level of abstraction using techniques such as naming conventions or by throwing errors when abstract methods are called. Here’s an example to demonstrate abstraction in JavaScript:

// Abstract class (using naming convention)
class Animal {
  constructor(name) {
    this.name = name;
  }

  // Abstract method
  makeSound() {
    throw new Error("Abstract method 'makeSound()' must be implemented.");
  }

  // Concrete method
  eat() {
    console.log(`${this.name} is eating.`);
  }
}

// Concrete class
class Dog extends Animal {
  makeSound() {
    console.log("Woof!");
  }
}

// Creating objects
const dog = new Dog("Buddy");
dog.makeSound(); // Output: Woof!
dog.eat(); // Output: Buddy is eating.

// Abstract class cannot be instantiated
const animal = new Animal("Generic"); // Throws an error

In the example above, the Animal class is defined as an abstract class using a naming convention by providing an implementation of the makeSound() method that throws an error. This indicates that the method must be implemented by any concrete subclass that extends Animal.

The Animal class also has a concrete method eat() that provides a default implementation and can be used by any subclass.

The Dog class extends the Animal class and provides an implementation for the makeSound() method.

When creating an instance of Dog (dog), we can call the makeSound() method, which outputs “Woof!”, and the eat() method, which outputs the eating behavior.

However, attempting to instantiate the abstract Animal class (const animal = new Animal(“Generic”)) will throw an error because abstract classes cannot be instantiated directly.

Although JavaScript doesn’t have native abstract classes or interfaces, you can use techniques like the one shown above to achieve a level of abstraction and enforce the implementation of certain methods by subclasses.