OOP: Is an enormously popular paradigm for structuring our complex code

  • Ease to add features and functionality
  • Easy for us and other developer to reason about (a clear structure)
  • Performant (efficient in terms of memory)

We need to organize our code as it gets more complex so it's not just an endless series of commans

The basics

Different ways to create objects

Creating Objects

There are multiple ways to create objects in JavaScript:

Link to original
]

Object literal

const user2 = {
name = "Julia";//assign properties to the object
user2.score=5;
increment=function(){
	score++;
	}
}
 
console.log(user2)

Object (.) notation

The dot notation allows us to bundle things to our object in a really elegant way.

const user2 = {} //create an empty object
 
user2.name = "Julia";//assign properties to the object
user2.score=5;
user2.increment=function(){
user2.score++;
}
 
console.log(user2)

Object.create

The Object.create() static method creates a new object, using an existing object as the prototype of the newly created object.

const person = {
  isHuman: false,
  printIntroduction: function () {
	console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  },
}
 
const me = Object.create(person);
 
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // Inherited properties can be overwritten
 
me.printIntroduction();
// Expected output: "My name is Matthew. Am I human? true"

Generate objects using functions

function userCreator(name, score) {
	 const newUser = {};
	 newUser.name = name;
	 newUser.score = score;
	 newUser.increment = function() {
		 newUser.score++;
	 };
 return newUser;
};
const user1 = userCreator("Phil", 4);
const user2 = userCreator("Julia", 5);
user1.increment()

The implementation above, It's simple and easy to reason about!

Each time we create a new user we make space in our computer's memory for all our data and functions. But our functions are just copies Is there a better way?

All the above solutions are pretty much untenable in terms of efficiency and memory rather than having a copy of our objects every time, we can do it in such a way that js understand when it does need to look something somewhere else in case it does not find it to its own object.

Store the increment function in just one object and have the interpreter, if it doesn't find the function on user1, look up to that object to check if it's there How to make this link?

function userCreator (name, score) {
	 const newUser = Object.create(userFunctionStore);
	 newUser.name = name;
	 newUser.score = score;
	 return newUser;
};
 
const userFunctionStore = {
	 increment: function(){this.score++;},
	 login: function(){console.log("You're loggedin");}
};
 
const user1 = userCreator("Phil", 4);
const user2 = userCreator("Julia", 5);
 
user1.increment();

[!warning ] The process above is very tedious, we don’t want to create empty objects all the time, and somehow use Object.create to add shared methods on the prototype, this is just a lot of work. for this we simply use the new keyword to automate all this!.

function UserCreator(name, score){
 this.name = name;
 this.score = score;
}
 
UserCreator.prototype.increment = function(){
 this.score++;
};
UserCreator.prototype.login = function(){
 console.log("login");
};
 
const user1 = new UserCreator(“Eva”, 9)
user1.increment()

The biggest gotchas of OOP

The biggest gotchas of oop

To have a function being shared between different objects, you simply put it on the prototype and the new will make sure it connects stuffs to go look in the prototype ! now here is a problem , what if it links to a function which on it’s turn have a function call within that gets called to return some kind of voodoo output ?

 

function UserCreator(name, score){ this.name = name; this.score = score; }UserCreator.prototype.increment = function(){ function add(){ this.score++; } add() }; UserCreator.prototype.login = function(){ console.log(“login”); }; const user1 = new UserCreator(“Daniel”, 9) user1.increment()

A simple solution would be to use the arrow function which simply has a this that refers to the this the parent was called in.

We need to introduce arrow functions - which bind this lexically

 
function UserCreator(name, score){
 this.name = name;
 this.score = score;
}
UserCreator.prototype.increment = function(){
 const add1 = ()=>{this.score++}
 add1()
};
UserCreator.prototype.login = function(){
 console.log("login");
};
const user1 = new UserCreator(“Eva”, 9)
user1.increment()

We’re writing our shared methods separately from our object ‘constructor’ itself (off in the User.prototype object)

Other languages let us do this all in one place. ES2015 lets us do so too

classes

Classes offer a syntactic sugar of all the process above

new vs this keyword

new Keyword:

In JavaScript, the new keyword is primarily used when creating objects from constructor functions. Constructor functions are like blueprints for creating objects. When you create an object with the new keyword, several important things happen:

  • A new empty object is created.
  • The this keyword within the constructor function refers to the newly created object.
  • The newly created object is linked to the constructor function’s prototype, which allows it to inherit properties and methods from the prototype.
  • The constructor function may modify the this object by adding properties and methods.
  • Finally, the new keyword returns the newly created object.

Here’s an example to illustrate the use of the new keyword:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
const john = new Person('John', 30);

In this example, new Person("John", 30):

  • Creates a new object.
  • Executes the Person constructor function with this referring to the new object.
  • Sets the name and age properties of the new object.
  • Returns the john object.

this Keyword:

The this keyword in JavaScript refers to the context in which a function is executed. When used within an object, this refers to that object. The behavior of this can vary depending on how a function is called:

  • Global Context: In the global scope (outside of any function or object), this refers to the global object, which is window in a browser or global in Node.js.

  • Method Context: When a function is a method of an object, this refers to the object that the method is called on. For example:

const person = {
  name: 'John',
  sayHello: function () {
    console.log(`Hello, my name is ${this.name}.`);
  },
};
person.sayHello(); // "Hello, my name is John.
  • Constructor Context: Inside a constructor function (as we saw with the new keyword), this refers to the newly created object.

  • Explicit Binding: You can explicitly set the value of this using methods like call() and apply().

  • Arrow Functions: Arrow functions do not have their own this. Instead, they inherit the this from the containing function.

While object literals and factory functions are convenient ways to generate objects, they are not ideal for creating objects that share some behaviors. This is because they can lead to duplicate code and make it difficult to maintain and extend your codebase. Examples of the problems with object literals and factory functions:

  • Duplicate code: If you need to create multiple objects with the same behaviors, you will have to duplicate the code for those behaviors in each object. This can make your code difficult to read and maintain.
  • Lack of encapsulation: Object literals and factory functions do not encapsulate the shared behaviors of your objects. This means that the code for those behaviors is exposed to the rest of your codebase, which can make it difficult to change or reuse the behaviors.
  • Difficulty extending: If you need to add new behaviors to your objects, you will have to modify the code for each object individually. This can be time-consuming and error-prone.

A better way to create objects that share some behaviors is to use classes. Classes allow you to encapsulate the shared behaviors of your objects and create a blueprint for creating new objects with those behaviors. This makes your code more readable, maintainable, and extensible.

Example of creating objects with shared behaviors using classes:

JavaScript

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
 
  greet() {
    return `Hello, ${this.name}!`;
  }
}
 
const user1 = new User('Alice', 'alice@example.com');
const user2 = new User('Bob', 'bob@example.com');
 
console.log(user1.greet()); // Hello, Alice!
console.log(user2.greet()); // Hello, Bob!

In this example, the User class encapsulates the shared behavior of greeting users. The greet() method is defined on the User class, so all instances of the User class will have access to it. This makes it easy to create new users and greet them without having to duplicate any code.

If you need to add new behaviors to all users, such as a method for updating their email address, you simply need to modify the User class. This will automatically update all instances of the User class.

Overall, classes are a better way to create objects that share some behaviors than object literals and factory functions. Classes make your code more readable, maintainable, and extensible.

tuneshare

more_vert