Skip to content

Latest commit

 

History

History
3557 lines (2438 loc) · 50.2 KB

File metadata and controls

3557 lines (2438 loc) · 50.2 KB


What is OOP

![[Media/Pasted image 20260609221102.png]]

![[Media/Pasted image 20260609221109.png]]


4 Fundamental OOP Principles

![[Media/Pasted image 20260609221114.png]] ![[Media/Pasted image 20260609221157.png]] ![[Media/Pasted image 20260609221203.png]] ![[Media/Pasted image 20260609221206.png]] ![[Media/Pasted image 20260609221211.png]]


Constructor Functions and the new Operator

Constructor functions are functions used to create multiple similar objects.

They act like a blueprint.

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);
const salaria = new Person('Salaria', 1998);

What Is a Constructor Function?

A constructor function is a normal function, but it is called with the new operator.

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

By convention, constructor function names start with a capital letter.

Person
Car
User
Account

[!tip] Convention
Use a capital first letter for constructor functions so people know they should be called with new.


Creating Objects with new

const ahmed = new Person('Ahmed', 1997);

This creates a new object:

Person {
  firstName: 'Ahmed',
  birthYear: 1997
}

Another object:

const salaria = new Person('Salaria', 1998);

Result:

Person {
  firstName: 'Salaria',
  birthYear: 1998
}

The new Operator in 4 Steps

When this runs:

const ahmed = new Person('Ahmed', 1997);

JavaScript does 4 things:

1. Creates a new empty object: {}
2. Sets this = {}
3. Links the object to the constructor's prototype
4. Returns the object automatically

[!important] Important
The new operator is what makes this point to the new object.


Step-by-Step

Step 1: Empty Object Is Created

{}

JavaScript creates a new empty object behind the scenes.


Step 2: this Points to That Object

Inside the constructor:

this.firstName = firstName;
this.birthYear = birthYear;

For this call:

new Person('Ahmed', 1997);

It becomes:

this.firstName = 'Ahmed';
this.birthYear = 1997;

So the empty object becomes:

{
  firstName: 'Ahmed',
  birthYear: 1997
}

Step 3: Object Is Linked to Prototype

The new object is linked to:

Person.prototype

This allows objects created from Person to access methods from Person.prototype.

ahmed.__proto__ === Person.prototype; // true

[!info] Note
This prototype link is what enables inheritance in JavaScript.


Step 4: Object Is Returned Automatically

You do not need to write:

return this;

JavaScript returns the new object automatically.

const ahmed = new Person('Ahmed', 1997);

Now ahmed stores the created object.


Full Example

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);
const salaria = new Person('Salaria', 1998);

console.log(ahmed);
console.log(salaria);

Output:

Person { firstName: 'Ahmed', birthYear: 1997 }
Person { firstName: 'Salaria', birthYear: 1998 }

this Inside Constructor Functions

Inside a constructor function:

this.firstName = firstName;

means:

Set firstName property on the new object

Example:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

When called with:

new Person('Ahmed', 1997);

this points to the new object being created.

[!success] Remember
In constructor functions, this refers to the new object created by new.


Constructor Functions Are Regular Functions

A constructor function is not a special type of function.

This is just a normal function:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

It becomes a constructor only because we call it with:

new Person('Ahmed', 1997);

[!important] Key Idea
The difference is not how the function is written.
The difference is how the function is called.


Do Not Forget new

Wrong:

const ahmed = Person('Ahmed', 1997);

Correct:

const ahmed = new Person('Ahmed', 1997);

Without new, this will not point to a new object.

In strict mode:

this

will be:

undefined

[!danger] Be Careful
Always use new when calling constructor functions.


Arrow Functions Cannot Be Constructors

This will not work:

const Person = (firstName, birthYear) => {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);

Arrow functions do not have their own this.

[!warning] Warning
Do not use arrow functions as constructor functions.

Use a normal function expression:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Checking with instanceof

console.log(ahmed instanceof Person);

Output:

true

This checks whether ahmed was created from the Person constructor.

ahmed instanceof Person; // true

[!tip] Tip
Use instanceof to check whether an object belongs to a constructor.


Do Not Create Methods Inside Constructor

Avoid this:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;

  this.calcAge = function () {
    return 2040 - this.birthYear;
  };
};

Why?

Every object gets its own copy of calcAge.

const a = new Person('A', 1997);
const b = new Person('B', 1998);

Both objects would store separate copies of the same method.

[!warning] Warning
Creating methods inside constructors wastes memory when many objects are created.

Better idea:

Person.prototype.calcAge = function () {
  return 2040 - this.birthYear;
};

Now all objects can share one method.


Constructor Function vs Object Literal

Object literal:

const ahmed = {
  firstName: 'Ahmed',
  birthYear: 1997,
};

Constructor function:

const ahmed = new Person('Ahmed', 1997);

Use constructor functions when you want to create many similar objects.

const user1 = new Person('Ahmed', 1997);
const user2 = new Person('Salaria', 1998);
const user3 = new Person('Ali', 2000);

Quick Syntax Summary

const ConstructorName = function (param1, param2) {
  this.property1 = param1;
  this.property2 = param2;
};

const objectName = new ConstructorName(value1, value2);

Example:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);

Common Mistakes

Forgetting new

const ahmed = Person('Ahmed', 1997);

Correct:

const ahmed = new Person('Ahmed', 1997);

Using Arrow Function

Wrong:

const Person = (firstName, birthYear) => {
  this.firstName = firstName;
};

Correct:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
};

Putting Methods Inside Constructor

Not recommended:

this.calcAge = function () {
  return 2040 - this.birthYear;
};

Better:

Person.prototype.calcAge = function () {
  return 2040 - this.birthYear;
};

Core Mental Model

A constructor function is a blueprint for creating objects.

const ahmed = new Person('Ahmed', 1997);

Means:

Create a new object
Set this to that object
Add properties to it
Link it to Person.prototype
Return the object

[!success] Remember
Constructor function + new = new object created from a blueprint.


Prototypes (OOP)

Prototype is JavaScript’s mechanism for sharing properties and methods between objects.

Every object has an internal link to another object called its prototype.

object → prototype → prototype → null

This chain is called the prototype chain.


Basic Setup

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);
const salaria = new Person('Salaria', 1998);

Here, Person is a constructor function.

ahmed
salaria

are objects created from Person.


What Is Person.prototype?

console.log(Person.prototype);

Output looks like:

{
  constructor: Person
}

Person.prototype is an object that will be used as the prototype for all objects created using:

new Person()

[!important] Important
Person.prototype is not the prototype of Person itself.
It is the prototype of objects created by new Person().


Adding Methods to Prototype

Instead of putting methods inside the constructor, we add them to the prototype.

Person.prototype.calcAge = function () {
  console.log(2047 - this.birthYear);
};

Now all Person objects can use this method.

ahmed.calcAge();   // 50
salaria.calcAge(); // 49

Why Add Methods to Prototype?

If we put a method inside the constructor:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;

  this.calcAge = function () {
    console.log(2047 - this.birthYear);
  };
};

Every object gets its own copy of calcAge.

That wastes memory.

Better:

Person.prototype.calcAge = function () {
  console.log(2047 - this.birthYear);
};

Now all objects share one method.

[!success] Benefit
Prototypes allow method sharing instead of method duplication.


The Prototype Link: __proto__

Every object has a prototype link.

console.log(ahmed.__proto__);

This points to:

Person.prototype

So this is true:

console.log(ahmed.__proto__ === Person.prototype); // true

[!tip] Mental Model
Person.prototype is the shared method box.
ahmed.__proto__ is the link to that box.


prototype vs __proto__

Term Meaning
Person.prototype Prototype object used for all instances
ahmed.__proto__ Link from instance to its prototype
Object.getPrototypeOf(ahmed) Modern way to read prototype link

Modern version:

console.log(Object.getPrototypeOf(ahmed));

Same as:

console.log(ahmed.__proto__);

[!warning] Warning
__proto__ works, but the modern recommended way is:

Object.getPrototypeOf(object);

Where Does __proto__ Come From?

When we use new:

const ahmed = new Person('Ahmed', 1997);

JavaScript does 4 steps:

1. Creates a new empty object: {}
2. Sets this to that empty object
3. Links the object to Person.prototype
4. Returns the object automatically

Step 3 creates this link:

ahmed.__proto__ === Person.prototype

Prototype Chain Lookup

When you run:

ahmed.calcAge();

JavaScript checks:

Does ahmed have calcAge directly?

No.

Then it checks:

Does ahmed.__proto__ have calcAge?

Yes.

So JavaScript uses the method from Person.prototype.

ahmed
  ↓
Person.prototype
  ↓
Object.prototype
  ↓
null

[!important] Important
If a property is not found on the object, JavaScript searches up the prototype chain.


isPrototypeOf()

console.log(Person.prototype.isPrototypeOf(ahmed));   // true
console.log(Person.prototype.isPrototypeOf(salaria)); // true
console.log(Person.prototype.isPrototypeOf(Person));  // false

This checks whether one object is in another object’s prototype chain.

Why Is This False?

Person.prototype.isPrototypeOf(Person); // false

Because Person.prototype is not the prototype of the constructor function Person.

It is the prototype of objects created by:

new Person()

[!warning] Naming Confusion
Person.prototype does not mean “prototype of Person”.
It means “prototype of objects created by Person”.


Adding Properties to Prototype

You can also add properties to the prototype.

Person.prototype.species = 'Homo Sapiens';

Now all instances can access it:

console.log(ahmed.species);   // Homo Sapiens
console.log(salaria.species); // Homo Sapiens

But species is not directly inside ahmed.

It lives in:

Person.prototype

Own Properties vs Inherited Properties

An own property is directly inside the object.

An inherited property comes from the prototype.

console.log(ahmed.hasOwnProperty('firstName')); // true
console.log(ahmed.hasOwnProperty('species'));   // false

Why?

firstName → directly on ahmed
species   → from Person.prototype

Example Object Structure

ahmed = {
  firstName: 'Ahmed',
  birthYear: 1997
}

But through prototype, it can access:

calcAge()
species

So:

ahmed.calcAge();
console.log(ahmed.species);

works even though those are not directly inside ahmed.


Full Example

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const ahmed = new Person('Ahmed', 1997);
const salaria = new Person('Salaria', 1998);

Person.prototype.calcAge = function () {
  console.log(2047 - this.birthYear);
};

ahmed.calcAge();   // 50
salaria.calcAge(); // 49

console.log(ahmed.__proto__ === Person.prototype); // true

console.log(Person.prototype.isPrototypeOf(ahmed));   // true
console.log(Person.prototype.isPrototypeOf(salaria)); // true
console.log(Person.prototype.isPrototypeOf(Person));  // false

Person.prototype.species = 'Homo Sapiens';

console.log(ahmed.species, salaria.species);

console.log(ahmed.hasOwnProperty('firstName')); // true
console.log(ahmed.hasOwnProperty('species'));   // false

Common Mistakes

Putting Methods Inside Constructor

Wrong:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;

  this.calcAge = function () {
    console.log(2047 - this.birthYear);
  };
};

Better:

Person.prototype.calcAge = function () {
  console.log(2047 - this.birthYear);
};

Thinking Person.prototype Is the Prototype of Person

Wrong idea:

Person.prototype is prototype of Person

Correct idea:

Person.prototype is prototype of objects created by new Person()

Confusing Own and Inherited Properties

ahmed.hasOwnProperty('firstName'); // true
ahmed.hasOwnProperty('species');   // false

species still works:

ahmed.species; // Homo Sapiens

because JavaScript finds it through the prototype chain.


Quick Syntax Summary

Constructor.prototype.methodName = function () {
  // shared method
};
Constructor.prototype.propertyName = value;
object.__proto__;
Object.getPrototypeOf(object);
Constructor.prototype.isPrototypeOf(object);
object.hasOwnProperty(propertyName);

Core Mental Model

Prototype = shared property/method storage.

When JavaScript cannot find a property directly on an object, it looks up the prototype chain.

ahmed.calcAge()

JavaScript checks:

1. ahmed
2. Person.prototype
3. Object.prototype
4. null

[!success] Remember
Constructor.prototype is the shared template.
instance.__proto__ is the link to that template.


Prototypes & How Prototypal Inheritance/Delgation Works

![[Media/Pasted image 20260610075848.png]]

![[Media/Pasted image 20260610075933.png]]

![[Media/Pasted image 20260610075957.png]]


Prototypal Inheritance on Built-In Objects

Built-in objects like arrays, functions, DOM elements, strings, etc. also use prototypes.

That means they can access methods from their prototype chain.

const arr = [3, 6, 6, 5, 6, 9, 9];

arr.map();
arr.filter();
arr.push();

These methods are not directly inside arr.
They come from:

Array.prototype

Prototype Chain Recap

Every object has an internal prototype link.

object.__proto__

Modern way:

Object.getPrototypeOf(object)

If JavaScript cannot find a property/method on the object itself, it searches up the prototype chain.

object
  ↓
object.__proto__
  ↓
object.__proto__.__proto__
  ↓
null

[!important] Important
Prototype chain = JavaScript looking for properties/methods in parent prototype objects.


Example with Person Object

Assume we already created this:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const jonas = new Person('Jonas', 1991);

Now:

console.log(jonas.__proto__);

This points to:

Person.prototype

So:

console.log(jonas.__proto__ === Person.prototype); // true

Going Up the Chain

console.log(jonas.__proto__);
console.log(jonas.__proto__.__proto__);
console.log(jonas.__proto__.__proto__.__proto__);

This means:

jonas.__proto__

Output:

Person.prototype

Then:

jonas.__proto__.__proto__

Output:

Object.prototype

Then:

jonas.__proto__.__proto__.__proto__

Output:

null

[!tip] Mental Model
The chain ends at null.

jonas
  ↓
Person.prototype
  ↓
Object.prototype
  ↓
null

Why Object.prototype?

Almost all normal objects in JavaScript eventually inherit from:

Object.prototype

That is why many objects can use methods like:

hasOwnProperty()
toString()
isPrototypeOf()

Example:

jonas.hasOwnProperty('firstName');

hasOwnProperty() is not inside jonas.

JavaScript finds it in:

Object.prototype

Constructor Property

console.dir(Person.prototype.constructor);

This points back to the constructor function:

Person

So:

Person.prototype.constructor === Person; // true

[!info] Note
The constructor property helps identify which constructor function created the prototype.


Arrays Also Use Prototypes

const arr = [3, 6, 6, 5, 6, 9, 9];

This array has access to many methods:

arr.map()
arr.filter()
arr.push()
arr.includes()

Because its prototype is:

Array.prototype

Array Prototype Chain

console.log(arr.__proto__);
console.log(arr.__proto__ === Array.prototype);

Output:

true

So:

arr.__proto__ === Array.prototype

means:

arr is linked to Array.prototype

Full chain:

arr
  ↓
Array.prototype
  ↓
Object.prototype
  ↓
null

Array Literal vs new Array()

These are both arrays:

const arr1 = [1, 2, 3];

const arr2 = new Array(1, 2, 3);

Usually prefer:

const arr = [1, 2, 3];

[!tip] Tip
[] is shorter and more common than new Array().


Where Do Array Methods Come From?

const arr = [1, 2, 3];

arr.map(num => num * 2);

map() is not stored directly on arr.

JavaScript searches:

1. arr
2. Array.prototype
3. Object.prototype
4. null

It finds map() on:

Array.prototype

[!success] Remember
Built-in methods like map, filter, push, and slice live on Array.prototype.


Adding Your Own Method to Array.prototype

Array.prototype.unique = function () {
  return [...new Set(this)];
};

Now every array can use:

const arr = [3, 6, 6, 5, 6, 9, 9];

console.log(arr.unique());

Output:

[3, 6, 5, 9]

How unique() Works

Array.prototype.unique = function () {
  return [...new Set(this)];
};

Inside the method:

this

points to the array that called the method.

So:

arr.unique()

means:

this → arr

Then:

new Set(this)

removes duplicates.

Then:

[...new Set(this)]

converts the Set back into an array.


Warning About Modifying Built-In Prototypes

Adding methods to built-in prototypes is possible, but usually not recommended.

Array.prototype.unique = function () {
  return [...new Set(this)];
};

[!danger] Be Careful
Do not modify built-in prototypes like Array.prototype in real projects unless you fully understand the risks.

Problems:

name conflicts
future JavaScript updates may break your method
libraries may behave unexpectedly
harder debugging

Better:

const unique = arr => [...new Set(arr)];

console.log(unique([3, 6, 6, 5, 9]));

DOM Elements Also Have Prototypes

const h1 = document.querySelector('h1');

h1 is an object too.

So it also has a prototype chain.

Example chain:

h1
  ↓
HTMLHeadingElement.prototype
  ↓
HTMLElement.prototype
  ↓
Element.prototype
  ↓
Node.prototype
  ↓
EventTarget.prototype
  ↓
Object.prototype
  ↓
null

That is why DOM elements can use methods like:

addEventListener()
querySelector()
remove()

They come from prototype objects higher in the chain.


Functions Also Have Prototypes

Functions are objects too.

console.dir(x => x + 1);

A function can use methods like:

call()
apply()
bind()

Because functions inherit from:

Function.prototype

Example:

const add = (a, b) => a + b;

add.call(null, 2, 3); // 5

[!important] Important
Arrays, functions, objects, and DOM elements all use prototypal inheritance.


prototype vs __proto__

Code Meaning
Array.prototype Shared prototype object for arrays
arr.__proto__ Link from array to Array.prototype
Person.prototype Shared prototype object for Person instances
jonas.__proto__ Link from jonas to Person.prototype

Modern way:

Object.getPrototypeOf(arr);
Object.getPrototypeOf(jonas);

[!warning] Warning
__proto__ is useful for learning, but in real code prefer:

Object.getPrototypeOf(obj)

Full Example

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const jonas = new Person('Jonas', 1991);

console.log(jonas.__proto__); 
console.log(jonas.__proto__.__proto__);
console.log(jonas.__proto__.__proto__.__proto__);

console.dir(Person.prototype.constructor);

const arr = [3, 6, 6, 5, 6, 9, 9];

console.log(arr.__proto__);
console.log(arr.__proto__ === Array.prototype);
console.log(arr.__proto__.__proto__);

Array.prototype.unique = function () {
  return [...new Set(this)];
};

console.log(arr.unique());

const h1 = document.querySelector('h1');

console.dir(x => x + 1);

Quick Syntax Summary

Object.getPrototypeOf(obj);
obj.__proto__;
Constructor.prototype;
Array.prototype.methodName = function () {
  // shared array method
};
arr.__proto__ === Array.prototype;
arr.__proto__.__proto__ === Object.prototype;

Core Mental Model

Built-in objects also inherit methods from prototypes.

For arrays:

arr
  ↓
Array.prototype
  ↓
Object.prototype
  ↓
null

For functions:

function
  ↓
Function.prototype
  ↓
Object.prototype
  ↓
null

For objects created by constructors:

jonas
  ↓
Person.prototype
  ↓
Object.prototype
  ↓
null

[!success] Remember
Built-in methods do not live directly on every object.
They are shared through prototypes.


ES6 Classes

ES6 classes are a cleaner syntax for creating objects and working with prototypes.

They are mostly syntactic sugar coat over constructor functions and prototypes.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
}

Class Declaration

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
}

This creates a class named PersonCl.

Then we can create objects using new:

const jessica = new PersonCl('Jessica Davis', 1996);

console.log(jessica);

Output:

PersonCl {
  _fullName: 'Jessica Davis',
  birthYear: 1996
}

Class Expression

Classes can also be written as expressions.

const PersonCl = class {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
};

[!tip] Tip
Class declarations are more common and easier to read.


constructor() Method

The constructor() method runs automatically when we create a new object.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
}

const jonas = new PersonCl('Jonas Schmedtmann', 1991);

When this runs:

new PersonCl('Jonas Schmedtmann', 1991)

JavaScript calls:

constructor('Jonas Schmedtmann', 1991)

[!important] Important
Each class can have only one constructor() method.


Instance Methods

Methods written inside a class are added to the class prototype.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }

  greet() {
    console.log(`Hey ${this.fullName}`);
  }
}

Usage:

const jonas = new PersonCl('Jonas Schmedtmann', 1991);

jonas.calcAge(); // 46
jonas.greet();   // Hey Jonas Schmedtmann

Methods Go to .prototype

This:

class PersonCl {
  calcAge() {
    console.log(2037 - this.birthYear);
  }
}

Is similar to:

PersonCl.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

So all objects created from the class share the same method.

jonas.__proto__ === PersonCl.prototype; // true

[!success] Benefit
Class methods are shared through the prototype, so each object does not get its own copy.


Getters

A getter allows us to read a method like a property.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  get age() {
    return 2037 - this.birthYear;
  }
}

Usage:

const jonas = new PersonCl('Jonas Schmedtmann', 1991);

console.log(jonas.age); // 46

Notice:

jonas.age

Not:

jonas.age()

[!important] Important
Getters are accessed like properties, not called like functions.


Setters

A setter runs when we try to set a property.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  set fullName(name) {
    if (name.includes(' ')) {
      this._fullName = name;
    } else {
      alert(`${name} is not a full name!`);
    }
  }

  get fullName() {
    return this._fullName;
  }
}

Usage:

const jessica = new PersonCl('Jessica Davis', 1996);

console.log(jessica.fullName); // Jessica Davis

Why Use _fullName?

Inside the constructor:

this.fullName = fullName;

This calls the setter:

set fullName(name) {
  this._fullName = name;
}

If we wrote this inside the setter:

this.fullName = name;

It would call the setter again and again forever.

So we use a different internal property:

this._fullName

[!warning] Warning
When a setter has the same name as a property, store the real value using a different name like _fullName.


Setter Validation

set fullName(name) {
  if (name.includes(' ')) {
    this._fullName = name;
  } else {
    alert(`${name} is not a full name!`);
  }
}

This checks whether the name contains a space.

Valid:

'Jessica Davis'

Invalid:

'Jessica'

Because a full name should contain at least two words.


Getter + Setter Together

set fullName(name) {
  if (name.includes(' ')) this._fullName = name;
  else alert(`${name} is not a full name!`);
}

get fullName() {
  return this._fullName;
}

The setter controls how the value is saved.

The getter controls how the value is read.

jessica.fullName = 'Jessica Davis'; // setter
console.log(jessica.fullName);      // getter

Full Example

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }

  greet() {
    console.log(`Hey ${this.fullName}`);
  }

  get age() {
    return 2037 - this.birthYear;
  }

  set fullName(name) {
    if (name.includes(' ')) {
      this._fullName = name;
    } else {
      alert(`${name} is not a full name!`);
    }
  }

  get fullName() {
    return this._fullName;
  }
}

const jessica = new PersonCl('Jessica Davis', 1996);

console.log(jessica);
jessica.calcAge();
jessica.greet();
console.log(jessica.age);
console.log(jessica.fullName);

Classes vs Constructor Functions

Feature Constructor Function ES6 Class
Object creation new Person() new PersonCl()
Methods Added manually to prototype Written inside class
Syntax Older Cleaner
Uses prototypes Yes Yes

Constructor function:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

Class version:

class PersonCl {
  constructor(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }
}

[!tip] Remember
Classes are cleaner syntax, but behind the scenes they still use prototypes.


Important Class Rules

Classes Are Not Hoisted

This does not work:

const jonas = new PersonCl('Jonas', 1991);

class PersonCl {
  constructor(name, birthYear) {
    this.name = name;
    this.birthYear = birthYear;
  }
}

Define the class first.


Classes Are First-Class Citizens

Classes can be stored in variables and passed around.

const Person = class {
  constructor(name) {
    this.name = name;
  }
};

This is possible because classes are special kinds of functions.


Classes Run in Strict Mode

All code inside a class automatically runs in strict mode.

[!info] Note
You do not need to write "use strict" inside a class.


Common Mistakes

Calling Class Without new

Wrong:

const jonas = PersonCl('Jonas', 1991);

Correct:

const jonas = new PersonCl('Jonas', 1991);

[!danger] Be Careful
Classes must be called with new.


Calling Getter Like a Function

Wrong:

jonas.age();

Correct:

jonas.age;

Getters are used like properties.


Forgetting _ in Setter

Wrong:

set fullName(name) {
  this.fullName = name;
}

This causes infinite recursion.

Correct:

set fullName(name) {
  this._fullName = name;
}

Quick Syntax Summary

class ClassName {
  constructor(param1, param2) {
    this.property1 = param1;
    this.property2 = param2;
  }

  methodName() {
    // instance method
  }

  get propertyName() {
    return value;
  }

  set propertyName(value) {
    this._propertyName = value;
  }
}

const objectName = new ClassName(value1, value2);

Core Mental Model

A class is a blueprint for creating objects.

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
}

Means:

Use this blueprint to create many Person objects

Most important idea:

class syntax → cleaner way to use constructor functions + prototypes

[!success] Remember
ES6 classes do not replace prototypes.
They are a cleaner syntax built on top of prototypes.


Static Methods (OOP)

Static methods are methods that belong to the class itself, not to the objects created from the class.

class PersonCl {
  static hey() {
    console.log('Hey there 👋');
  }
}

You call it like this:

PersonCl.hey();

Not like this:

const jessica = new PersonCl('Jessica Davis', 1996);

jessica.hey(); // Error

Basic Syntax

class ClassName {
  static methodName() {
    // code
  }
}

Usage:

ClassName.methodName();

[!important] Important
Static methods are called on the class, not on instances.


Example

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }

  static hey() {
    console.log('Hey there 👋');
    console.log(this);
  }
}

const jessica = new PersonCl('Jessica Davis', 1996);

jessica.calcAge(); // works
PersonCl.hey();    // works

But this does not work:

jessica.hey(); // Error

Instance Methods vs Static Methods

Type Belongs To Called On
Instance method Object instances jessica.calcAge()
Static method Class itself PersonCl.hey()

Example:

class PersonCl {
  calcAge() {
    console.log(2037 - this.birthYear);
  }

  static hey() {
    console.log('Hey there 👋');
  }
}
const jessica = new PersonCl('Jessica Davis', 1996);

jessica.calcAge(); // instance method
PersonCl.hey();    // static method

Where Are Static Methods Stored?

Instance methods are added to:

PersonCl.prototype

Static methods are added directly to:

PersonCl

So:

PersonCl.hey();

works because hey exists on the class itself.

But:

jessica.hey();

does not work because hey is not on PersonCl.prototype.

[!tip] Mental Model
Static method = utility function attached to the class.


this Inside Static Methods

static hey() {
  console.log(this);
}

When called like this:

PersonCl.hey();

Inside hey():

this → PersonCl

So this refers to the class itself.

[!warning] Warning
In a static method, this does not refer to an instance like jessica.
It refers to the class that called the method.


Why Use Static Methods?

Use static methods for utility/helper behavior related to a class, but not dependent on one specific object.

Example:

class PersonCl {
  static species() {
    return 'Homo Sapiens';
  }
}

console.log(PersonCl.species()); // Homo Sapiens

This does not need a specific person object.


Built-In Static Method Example

JavaScript already uses static methods.

Array.from('Jonas');

Output:

['J', 'o', 'n', 'a', 's']

from() is a static method on Array.

Correct:

Array.from('Jonas');

Wrong:

[1, 2, 3].from(); // Error

Because from() belongs to Array, not to array instances.


Static Method with Constructor Functions

Before class syntax, static methods could be added directly to constructor functions.

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Person.hey = function () {
  console.log('Hey there 👋');
};

Person.hey();

This is similar to:

class PersonCl {
  static hey() {
    console.log('Hey there 👋');
  }
}

Static vs Prototype

PersonCl.prototype.greet = function () {
  console.log(`Hey ${this.fullName}`);
};

This is available to objects:

jessica.greet();

But:

static hey() {
  console.log('Hey there 👋');
}

is available only on the class:

PersonCl.hey();

[!important] Important
Prototype methods are inherited by instances.
Static methods are not inherited by instances.


Common Mistake

Wrong:

const jessica = new PersonCl('Jessica Davis', 1996);

jessica.hey();

Correct:

PersonCl.hey();

Because hey() is static.


Quick Syntax Summary

class ClassName {
  constructor(value) {
    this.value = value;
  }

  instanceMethod() {
    console.log(this.value);
  }

  static staticMethod() {
    console.log('Static method');
  }
}

const obj = new ClassName('Hello');

obj.instanceMethod();

ClassName.staticMethod();

Core Mental Model

Static methods belong to the class, not to objects created from the class.

PersonCl.hey();

means:

Call method on the class itself
jessica.calcAge();

means:

Call method on an object instance

[!success] Remember
Instance method = for individual objects.
Static method = for the class itself.


Object.create (OOP)

Object.create() creates a new object and manually sets its prototype.

It is another way to implement prototypal inheritance in JavaScript.

const obj = Object.create(prototypeObject);

Basic Idea

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },
};

const steven = Object.create(PersonProto);

This means:

steven.__proto__ === PersonProto

So steven can use methods from PersonProto.


Syntax

Object.create(proto/parentobject);

Example:

const childObject = Object.create(parentObject);

Meaning:

Create a new object
Link its prototype to parentObject

[!important] Important
Object.create() does not call a constructor function.
It directly links one object to another object.


Example

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },
};

const steven = Object.create(PersonProto);

steven.name = 'Steven';
steven.birthYear = 2002;

steven.calcAge(); // 35

Here, calcAge() is not directly inside steven.

JavaScript finds it in:

PersonProto

Prototype Link

console.log(steven.__proto__ === PersonProto);

Output:

true

This means:

steven
  ↓
PersonProto
  ↓
Object.prototype
  ↓
null

[!tip] Mental Model
Object.create(PersonProto) means:
create an empty object whose parent is PersonProto.


Adding Properties Manually

const steven = Object.create(PersonProto);

steven.name = 'Steven';
steven.birthYear = 2002;

This works, but it is repetitive if we create many objects.

Better: create an init() method.


Using an init() Method

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },

  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

Now we can do:

const sarah = Object.create(PersonProto);

sarah.init('Sarah', 1979);
sarah.calcAge(); // 58

Why init()?

Object.create() does not automatically run a constructor.

So this does not exist:

constructor()

Instead, we manually create an initializer method:

init(firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
}

[!info] Note
init() is not special JavaScript syntax.
It is just a normal method we created.


Full Example

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },

  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

const steven = Object.create(PersonProto);

steven.name = 'Steven';
steven.birthYear = 2002;
steven.calcAge();

console.log(steven.__proto__ === PersonProto); // true

const sarah = Object.create(PersonProto);

sarah.init('Sarah', 1979);
sarah.calcAge();

Object.create() vs Constructor Function

Constructor function:

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

const jonas = new Person('Jonas', 1991);

Object.create():

const PersonProto = {
  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

const jonas = Object.create(PersonProto);
jonas.init('Jonas', 1991);
Feature Constructor Function Object.create()
Uses new Yes No
Uses constructor Yes No
Sets prototype Automatically Manually
Main idea Create from function blueprint Create from prototype object

Object.create() vs Class

Class:

class PersonCl {
  constructor(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }
}

Object.create():

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },
};

[!success] Key Difference
Classes and constructor functions create objects from a blueprint.
Object.create() creates objects directly from another object.


Why Use Object.create()?

It is useful for understanding JavaScript’s prototype system clearly.

const steven = Object.create(PersonProto);

This directly shows:

steven inherits from PersonProto

No constructor function.
No class syntax.
Just direct prototype linking.


Common Mistake

Thinking Object.create() copies properties

const steven = Object.create(PersonProto);

This does not copy methods from PersonProto.

It links steven to PersonProto.

steven.calcAge()

works because JavaScript searches the prototype chain.

[!warning] Warning
Object.create() creates a prototype link.
It does not clone the prototype object.


Quick Syntax Summary

const parentObject = {
  method() {
    console.log('Hello');
  },
};

const childObject = Object.create(parentObject);

childObject.method();

With initializer:

const PersonProto = {
  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },

  calcAge() {
    console.log(2037 - this.birthYear);
  },
};

const person = Object.create(PersonProto);

person.init('Sarah', 1979);
person.calcAge();

Core Mental Model

Object.create() creates a new object and sets its prototype manually.

const steven = Object.create(PersonProto);

Means:

Create steven
Link steven to PersonProto
Let steven use methods from PersonProto

[!success] Remember
Object.create() is the most direct way to create prototypal inheritance in JavaScript.


Inheritance Between "Classes": Constructor Functions

Inheritance means one constructor function can reuse properties and methods from another constructor function.

Example idea:

Person → parent constructor
Student → child constructor

A Student is also a Person, but with extra data like course.


Goal

We want this:

const mike = new Student('Mike', 2020, 'Computer Science');

mike.introduce();
mike.calcAge();

introduce() should come from:

Student.prototype

calcAge() should come from:

Person.prototype

Parent Constructor: Person

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

This creates common properties for every person:

firstName
birthYear

Parent Prototype Method

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

This method is shared by all Person objects.

const jonas = new Person('Jonas', 1991);

jonas.calcAge();

[!tip] Remember
Methods should usually go on the prototype, not inside the constructor.


Child Constructor: Student

const Student = function (firstName, birthYear, course) {
  Person.call(this, firstName, birthYear);
  this.course = course;
};

Here, Student creates:

firstName
birthYear
course

But firstName and birthYear are created by reusing the Person constructor.


Why Person.call(this, ...)?

Inside Student, this line:

Person.call(this, firstName, birthYear);

means:

Run Person constructor
but make this point to the new Student object

So this:

const mike = new Student('Mike', 2020, 'Computer Science');

creates:

{
  firstName: 'Mike',
  birthYear: 2020,
  course: 'Computer Science'
}

[!important] Important
We use call() because we need to manually set this.

Wrong:

Person(firstName, birthYear);

Correct:

Person.call(this, firstName, birthYear);

Linking Prototypes

To let students use Person.prototype methods:

Student.prototype = Object.create(Person.prototype);

This creates a new empty object whose prototype is Person.prototype.

So the chain becomes:

mike
  ↓
Student.prototype
  ↓
Person.prototype
  ↓
Object.prototype
  ↓
null

[!success] Key Idea
Object.create(Person.prototype) links Student.prototype to Person.prototype.


Add Student-Specific Methods

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

Now all students can use:

mike.introduce();

Output:

My name is Mike and I study Computer Science

Full Code

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

const Student = function (firstName, birthYear, course) {
  Person.call(this, firstName, birthYear);
  this.course = course;
};

Student.prototype = Object.create(Person.prototype);

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

const mike = new Student('Mike', 2020, 'Computer Science');

mike.introduce();
mike.calcAge();

Output:

My name is Mike and I study Computer Science
17

Why Link Prototypes Before Adding Methods?

Correct order:

Student.prototype = Object.create(Person.prototype);

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

Wrong order:

Student.prototype.introduce = function () {};

Student.prototype = Object.create(Person.prototype);

Why wrong?

Because this line:

Student.prototype = Object.create(Person.prototype);

replaces the whole Student.prototype object.

So any methods added before it will be lost.

[!warning] Warning
Link prototypes first, then add child-specific methods.


Do Not Do This

Student.prototype = Person.prototype;

This is wrong because both constructors would share the exact same prototype object.

That means changing Student.prototype would also affect Person.prototype.

Correct:

Student.prototype = Object.create(Person.prototype);

This creates a new object linked to Person.prototype.

[!danger] Be Careful
Use Object.create(), not direct assignment.


Prototype Chain in Action

When this runs:

mike.calcAge();

JavaScript searches:

1. Does mike have calcAge?
2. Does Student.prototype have calcAge?
3. Does Person.prototype have calcAge?

It finds calcAge() in:

Person.prototype

So mike can use it.


Checking the Prototype Chain

console.log(mike.__proto__);
console.log(mike.__proto__.__proto__);

This gives:

mike.__proto__                → Student.prototype
mike.__proto__.__proto__      → Person.prototype

Modern version:

Object.getPrototypeOf(mike);
Object.getPrototypeOf(Object.getPrototypeOf(mike));

[!tip] Tip
__proto__ is useful for learning, but Object.getPrototypeOf() is the modern way.


instanceof

console.log(mike instanceof Student);
console.log(mike instanceof Person);
console.log(mike instanceof Object);

Output:

true
true
true

Why?

Because the prototype chain is:

mike
  ↓
Student.prototype
  ↓
Person.prototype
  ↓
Object.prototype

So mike is considered an instance of all three.


Fixing the constructor Property

After this line:

Student.prototype = Object.create(Person.prototype);

The constructor property becomes incorrect.

So we fix it:

Student.prototype.constructor = Student;

Then:

console.dir(Student.prototype.constructor);

points back to:

Student

[!important] Important
After manually replacing a prototype, reset the constructor property.


Final Correct Version

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

const Student = function (firstName, birthYear, course) {
  Person.call(this, firstName, birthYear);
  this.course = course;
};

Student.prototype = Object.create(Person.prototype);

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

Student.prototype.constructor = Student;

const mike = new Student('Mike', 2020, 'Computer Science');

mike.introduce();
mike.calcAge();

console.log(mike instanceof Student); // true
console.log(mike instanceof Person);  // true
console.log(mike instanceof Object);  // true

Common Mistakes

Forgetting call()

Wrong:

Person(firstName, birthYear);

Correct:

Person.call(this, firstName, birthYear);

Without call(), this will not point to the new Student object.


Linking Prototype Too Late

Wrong:

Student.prototype.introduce = function () {};

Student.prototype = Object.create(Person.prototype);

Correct:

Student.prototype = Object.create(Person.prototype);

Student.prototype.introduce = function () {};

Forgetting to Reset Constructor

After linking:

Student.prototype = Object.create(Person.prototype);

Add:

Student.prototype.constructor = Student;

Quick Syntax Summary

const Parent = function (prop1, prop2) {
  this.prop1 = prop1;
  this.prop2 = prop2;
};

Parent.prototype.parentMethod = function () {
  // shared parent method
};

const Child = function (prop1, prop2, childProp) {
  Parent.call(this, prop1, prop2);
  this.childProp = childProp;
};

Child.prototype = Object.create(Parent.prototype);

Child.prototype.childMethod = function () {
  // shared child method
};

Child.prototype.constructor = Child;

Core Mental Model

Constructor inheritance has two jobs:

1. Inherit properties
2. Inherit methods

Properties are inherited using:

Parent.call(this, ...)

Methods are inherited using:

Child.prototype = Object.create(Parent.prototype);

[!success] Remember
call() connects constructor properties.
Object.create() connects prototype methods.