Advanced JavaScript introduces powerful concepts used in modern applications, frameworks, libraries, and application architecture.
This module covers:
These concepts help us understand how JavaScript creates objects, shares behavior, iterates through data, and reuses code.
A class is a blueprint for creating objects.
Imagine we need multiple users.
Without classes:
const user1 = {
name: "Samir",
age: 21
};
const user2 = {
name: "Ram",
age: 20
};
const user3 = {
name: "Hari",
age: 22
};
We repeatedly create objects with the same structure.
Instead, we can create a class:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Then create as many users as needed:
const user1 =
new User("Samir", 21);
const user2 =
new User("Ram", 20);
Think of it as:
Class
↓
Blueprint
↓
Create Objects
↓
Instances
Basic syntax:
class User {
}
A class is created using the:
class
keyword.
Example:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Objects created from a class are called instances.
Example:
const user1 =
new User(
"Samir",
21
);
Output:
User {
name: "Samir",
age: 21
}
Another instance:
const user2 =
new User(
"Ram",
20
);
Now:
User Class
│
├── user1
│
└── user2
Each object has its own data.
new KeywordThe new keyword creates a new instance of a class.
Example:
const user =
new User(
"Samir",
21
);
Conceptually:
new
↓
Create Object
↓
Run constructor()
↓
Set Properties
↓
Return Instance
The constructor() is a special method inside a class.
It runs automatically when a new object is created.
Example:
class Car {
constructor(brand) {
this.brand = brand;
}
}
Create an object:
const car =
new Car("Toyota");
The constructor receives:
"Toyota"
and stores it in:
this.brand
Result:
Car {
brand: "Toyota"
}
this in ClassesInside a class, this usually refers to the current instance.
Example:
class User {
constructor(name) {
this.name = name;
}
}
When we write:
const user =
new User("Samir");
then:
this
↓
user
Therefore:
this.name = name;
becomes conceptually:
user.name = "Samir";
Classes can contain methods.
Methods are functions associated with the class instances.
Example:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(
`Hello ${this.name}`
);
}
}
Usage:
const user =
new User("Samir");
user.greet();
Output:
Hello Samir
A class can contain many methods.
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
}
Create an instance:
const calculator =
new Calculator();
Usage:
console.log(
calculator.add(10, 5)
);
console.log(
calculator.multiply(10, 5)
);
Output:
15
50
Before the class syntax was introduced, constructor functions were commonly used.
Constructor function:
function User(name) {
this.name = name;
}
Usage:
const user =
new User("Samir");
Modern class syntax:
class User {
constructor(name) {
this.name = name;
}
}
Usage:
const user =
new User("Samir");
Both can create objects.
The class syntax is generally easier to read when writing object-oriented code.
An important thing to understand is:
Classes
↓
Cleaner Syntax
↓
Built on JavaScript's
Prototype System
Classes did not replace prototypes.
They provide a more convenient syntax for working with prototype-based behavior.
An iterator is an object that provides values one at a time.
Think of it as:
Collection
↓
Value 1
↓
Value 2
↓
Value 3
↓
Done
Iterators allow JavaScript to move sequentially through data.
Iterators are useful for:
JavaScript uses iteration heavily with:
for...of
and many built-in iterable objects.
Consider an array:
const numbers = [
10,
20,
30
];
Arrays are iterable.
We can get their iterator using:
const iterator =
numbers[
Symbol.iterator
]();
Now:
console.log(
iterator.next()
);
Output:
{
value: 10,
done: false
}
next() MethodAn iterator provides a:
next()
method.
Each call returns an object containing:
{
value: something,
done: false
}
Example:
iterator.next();
Output:
{
value: 10,
done: false
}
Next call:
iterator.next();
Output:
{
value: 20,
done: false
}
Next:
iterator.next();
Output:
{
value: 30,
done: false
}
After all values are consumed:
iterator.next();
Output:
{
value: undefined,
done: true
}
value and doneIterator results normally contain:
| Property | Meaning |
|---|---|
value |
Current value |
done |
Whether iteration has finished |
Example:
{
value: 10,
done: false
}
means:
Current Value = 10
More Values Exist
While:
{
value: undefined,
done: true
}
means:
No More Values
↓
Iteration Complete
These two terms are related but different.
An iterable is something that knows how to create an iterator.
Examples include:
Array
String
Map
Set
An iterator is the object that produces values using:
next()
Conceptually:
Iterable
↓
Symbol.iterator
↓
Iterator
↓
next()
↓
Values
for...of Uses IteratorsConsider:
const numbers = [
10,
20,
30
];
for (const num of numbers) {
console.log(num);
}
Output:
10
20
30
Behind the scenes, JavaScript uses the object's iteration mechanism to retrieve values.
Conceptually:
for...of
↓
Get Iterator
↓
next()
↓
10
↓
next()
↓
20
↓
next()
↓
30
↓
done = true
Strings can also be iterated.
const name = "Sam";
for (const letter of name) {
console.log(letter);
}
Output:
S
a
m
Because strings implement the iterable protocol.
We can create our own iterable object.
Example:
const range = {
current: 1,
last: 3,
[Symbol.iterator]() {
return this;
},
next() {
if (
this.current <=
this.last
) {
return {
value:
this.current++,
done: false
};
}
return {
done: true
};
}
};
Now:
for (const num of range) {
console.log(num);
}
Output:
1
2
3
Our object now works with:
for...of
because it implements the iteration protocol.
Creating custom iterators manually can require a lot of code.
Generators make this easier.
A generator is a special function that can:
Start
↓
Pause
↓
Resume
↓
Pause
↓
Resume
↓
Finish
Unlike normal functions, generators can pause their execution.
A generator function uses:
function*
Example:
function* numbers() {
}
Notice the:
*
after function.
function* numbers() {
yield 1;
yield 2;
yield 3;
}
Calling the generator:
const gen =
numbers();
Calling a generator does not immediately execute the entire function.
Instead, it returns a generator object.
next() with Generatorsconsole.log(
gen.next()
);
Output:
{
value: 1,
done: false
}
Again:
console.log(
gen.next()
);
Output:
{
value: 2,
done: false
}
Again:
console.log(
gen.next()
);
Output:
{
value: 3,
done: false
}
One more call:
console.log(
gen.next()
);
Output:
{
value: undefined,
done: true
}
yield KeywordThe yield keyword pauses a generator.
Example:
function* test() {
console.log("A");
yield 1;
console.log("B");
yield 2;
console.log("C");
}
Create generator:
const gen =
test();
First:
gen.next();
Output:
A
The function pauses at:
yield 1;
Call again:
gen.next();
Output:
B
The generator resumes from where it stopped.
Call again:
gen.next();
Output:
C
Normal function:
function test() {
console.log("A");
console.log("B");
console.log("C");
}
Calling:
test();
executes everything:
A
B
C
Generator:
function* test() {
console.log("A");
yield;
console.log("B");
yield;
console.log("C");
}
Execution can happen gradually:
next()
↓
A
next()
↓
B
next()
↓
C
Generators work well with loops.
function* count() {
let i = 1;
while (i <= 5) {
yield i++;
}
}
Usage:
for (
const num of count()
) {
console.log(num);
}
Output:
1
2
3
4
5
Generators can even represent sequences that have no fixed end.
Example:
function* counter() {
let number = 1;
while (true) {
yield number++;
}
}
Usage:
const numbers =
counter();
console.log(
numbers.next().value
);
console.log(
numbers.next().value
);
console.log(
numbers.next().value
);
Output:
1
2
3
The generator only produces values when requested.
Generators can be useful for:
The important idea is:
Generator
↓
Does Not Need Everything At Once
↓
Produces Values When Requested
JavaScript is fundamentally a prototype-based language.
Objects can inherit properties and methods from other objects through prototypes.
Example:
const user = {
name: "Samir"
};
We only defined:
name
But this works:
user.toString();
Where did:
toString()
come from?
It comes through the object's prototype chain.
We can inspect an object's prototype using:
Object.getPrototypeOf(
user
);
Example:
const user = {
name: "Samir"
};
console.log(
Object.getPrototypeOf(
user
)
);
For a normal object, its prototype is typically:
Object.prototype
JavaScript looks for properties through a chain of objects.
Example:
user
↓
Object.prototype
↓
null
Suppose:
user.toString();
JavaScript searches:
Does user have toString()?
↓
No
Check Object.prototype
↓
Found toString()
↓
Execute It
This lookup process is called the:
Prototype Chain
Consider:
const user = {
name: "Samir"
};
When we write:
console.log(
user.name
);
JavaScript finds name directly on user.
But:
user.toString();
is different.
Conceptually:
user
│
├── name
│
└── No toString()
↓
Object.prototype
│
└── toString()
JavaScript continues searching until:
Property Found
or:
Prototype = null
Consider a traditional constructor function:
function User(name) {
this.name = name;
}
Create users:
const user1 =
new User("Samir");
const user2 =
new User("Ram");
We can add shared behavior through:
User.prototype
Example:
User.prototype.greet =
function() {
console.log(
`Hello ${this.name}`
);
};
Now:
user1.greet();
user2.greet();
Output:
Hello Samir
Hello Ram
Consider this:
function User(name) {
this.name = name;
this.greet =
function() {
console.log(
this.name
);
};
}
Every instance gets its own function.
Conceptually:
user1
↓
Own greet()
user2
↓
Own greet()
user3
↓
Own greet()
With a prototype:
function User(name) {
this.name = name;
}
User.prototype.greet =
function() {
console.log(
this.name
);
};
Conceptually:
User.prototype
│
greet()
/ | \
/ | \
↓ ↓ ↓
user1 user2 user3
The behavior is shared through the prototype.
Consider:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(
this.name
);
}
}
The greet() method is available through:
User.prototype
We can check:
console.log(
User.prototype.greet
);
This is why we say:
Class Syntax
↓
Built on
↓
Prototype Mechanism
Inheritance allows one class to reuse and extend the behavior of another class.
Imagine:
Person
│
├── Student
├── Teacher
└── Developer
All of them may have:
name
age
greet()
Instead of repeating the same code, we can create a parent class.
Example:
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(
`Hello ${this.name}`
);
}
}
Now other classes can inherit from Person.
Use:
extends
to create a child class.
class Student
extends Person {
}
Now:
const student =
new Student("Samir");
The student can use:
student.greet();
Output:
Hello Samir
Even though Student does not directly define greet().
It inherits it from:
Person
extends KeywordThe extends keyword creates inheritance between classes.
Example:
class Animal {
speak() {
console.log(
"Animal Sound"
);
}
}
class Dog
extends Animal {
}
Usage:
const dog =
new Dog();
dog.speak();
Output:
Animal Sound
Conceptually:
Animal
↓
speak()
↑ inherited by
Dog
A child class can have its own constructor.
Example:
class Person {
constructor(name) {
this.name = name;
}
}
class Student
extends Person {
constructor(
name,
course
) {
super(name);
this.course =
course;
}
}
Create an instance:
const student =
new Student(
"Samir",
"BSc.CSIT"
);
Now:
console.log(
student.name
);
console.log(
student.course
);
Output:
Samir
BSc.CSIT
super() KeywordWhen a child class defines a constructor, super() is used to call the parent constructor before accessing this.
Example:
class Person {
constructor(name) {
this.name = name;
}
}
Child:
class Student
extends Person {
constructor(
name,
course
) {
super(name);
this.course =
course;
}
}
Here:
super(name);
calls:
Person
↓
constructor(name)
Conceptually:
new Student("Samir", "BSc.CSIT")
↓
Student constructor
↓
super("Samir")
↓
Person constructor
↓
this.name = "Samir"
↓
Back to Student
↓
this.course = "BSc.CSIT"
A child class can have additional methods.
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(
`Hello ${this.name}`
);
}
}
class Student
extends Person {
study() {
console.log(
"Studying..."
);
}
}
Usage:
const student =
new Student("Samir");
student.greet();
student.study();
Output:
Hello Samir
Studying...
The student has access to:
greet()
↓
Inherited
study()
↓
Own Method
A child class can replace a method inherited from its parent.
This is called method overriding.
Parent:
class Person {
greet() {
console.log(
"Hello"
);
}
}
Child:
class Student
extends Person {
greet() {
console.log(
"Hello Student"
);
}
}
Usage:
const student =
new Student();
student.greet();
Output:
Hello Student
The child's method takes priority.
supersuper can also call methods from the parent class.
Example:
class Person {
greet() {
console.log(
"Hello"
);
}
}
class Student
extends Person {
greet() {
super.greet();
console.log(
"I am a student"
);
}
}
Usage:
const student =
new Student();
student.greet();
Output:
Hello
I am a student
Here:
super.greet();
calls the parent's greet() method.
Classes and inheritance still use prototypes underneath.
Consider:
class Person {
}
class Student
extends Person {
}
const student =
new Student();
The prototype chain looks conceptually like:
student
↓
Student.prototype
↓
Person.prototype
↓
Object.prototype
↓
null
When JavaScript searches for a method:
student
↓
Student.prototype
↓
Person.prototype
↓
Object.prototype
↓
null
It stops when the property is found.
instanceof OperatorThe instanceof operator checks whether an object belongs to a constructor/class's prototype chain.
Example:
class Person {
}
class Student
extends Person {
}
const student =
new Student();
Check:
student instanceof Student;
Output:
true
Also:
student instanceof Person;
Output:
true
Because:
Student
↓
inherits from
↓
Person
instanceof Exampleclass Animal {
}
class Dog
extends Animal {
}
const dog =
new Dog();
Check:
console.log(
dog instanceof Dog
);
Output:
true
Check:
console.log(
dog instanceof Animal
);
Output:
true
Check:
console.log(
dog instanceof Array
);
Output:
false
Suppose we are creating different types of vehicles.
Parent class:
class Vehicle {
constructor(brand) {
this.brand = brand;
}
start() {
console.log(
`${this.brand} started`
);
}
}
Child class:
class Car
extends Vehicle {
constructor(
brand,
doors
) {
super(brand);
this.doors = doors;
}
honk() {
console.log(
"Beep"
);
}
}
Create object:
const car =
new Car(
"Toyota",
4
);
Usage:
car.start();
car.honk();
console.log(
car.doors
);
Output:
Toyota started
Beep
4
Conceptually:
Vehicle
│
├── brand
└── start()
↑
│
Car
│
├── doors
└── honk()
Classes and prototypes are not competing systems.
JavaScript classes are built on prototypes.
| Classes | Prototypes |
|---|---|
| Modern syntax | Core mechanism |
| Easier to read | Lower-level concept |
Uses class |
Uses .prototype |
Uses extends |
Uses prototype chain |
| Introduced in ES6 | Fundamental JavaScript behavior |
| Built on prototypes | Powers inheritance |
Think of it as:
Class Syntax
↓
Convenient Interface
↓
Prototype System
↓
Actual Inheritance Mechanism
Iterators and generators are closely related.
An iterator manually provides:
next()
Example:
const iterator = {
next() {
return {
value: 1,
done: false
};
}
};
A generator automatically creates iterator behavior:
function* numbers() {
yield 1;
yield 2;
}
Comparison:
| Iterator | Generator |
|---|---|
Uses next() |
Also provides next() |
| Can be created manually | Created with function* |
| Manual state management | State managed automatically |
| More verbose | Cleaner for custom sequences |
Returns {value, done} |
Returns {value, done} |
Here is a complete example combining several concepts.
class User {
constructor(
name,
email
) {
this.name = name;
this.email = email;
}
introduce() {
console.log(
`I am ${this.name}`
);
}
}
class Admin
extends User {
constructor(
name,
email,
role
) {
super(
name,
email
);
this.role = role;
}
showRole() {
console.log(
`Role: ${this.role}`
);
}
}
Create an admin:
const admin =
new Admin(
"Samir",
"samir@example.com",
"Administrator"
);
Usage:
admin.introduce();
admin.showRole();
Output:
I am Samir
Role: Administrator
Here:
User
↓
Parent Class
↓
name
email
introduce()
Admin
↓
Child Class
↓
Inherits User
+
role
showRole()
Used for creating structured objects and reusable behavior.
Models
Services
Libraries
Custom Data Structures
Object-Oriented Code
Used when working with iterable data:
Arrays
Strings
Maps
Sets
Custom Collections
Useful for controlled and lazy sequences:
Lazy Data Generation
Custom Iteration
Sequences
Large Data Processing
They power JavaScript's object inheritance system.
Objects
Arrays
Functions
Classes
Built-in Methods
Used when related objects share common behavior.
Person
↓
Student
Vehicle
↓
Car
User
↓
Admin
Use classes when they make your object model clearer.
class User {
constructor(name) {
this.name = name;
}
}
Keep classes focused on a clear responsibility.
Prefer meaningful names:
class User {}
class Product {}
class ShoppingCart {}
Avoid:
class Data {}
class Thing {}
class Manager {}
unless those names genuinely describe the domain.
Use inheritance when there is a real:
"is-a"
relationship.
For example:
Car is a Vehicle
Student is a Person
Admin is a User
Avoid creating deep inheritance chains when simpler composition would make the code easier to understand.
Understand prototypes even when using classes because classes ultimately depend on the prototype system.
newWrong:
const user =
User("Samir");
For a class, use:
const user =
new User("Samir");
super()When a derived class defines a constructor, call super() before using this.
Wrong:
class Student
extends Person {
constructor(name) {
this.name = name;
}
}
Correct:
class Student
extends Person {
constructor(name) {
super(name);
}
}
An infinite generator:
function* numbers() {
let i = 1;
while (true) {
yield i++;
}
}
never naturally finishes.
So code consuming it must have a stopping condition.
Example:
const gen =
numbers();
for (
let i = 0;
i < 5;
i++
) {
console.log(
gen.next().value
);
}
Output:
1
2
3
4
5
| Concept | Purpose |
|---|---|
| Class | Blueprint for creating objects |
| Instance | Object created from a class |
new |
Create class instance |
| Constructor | Initialize an instance |
| Method | Behavior associated with a class |
| Iterator | Access values sequentially |
next() |
Get next iterator value |
Symbol.iterator |
Defines iterable behavior |
| Generator | Function that can pause and resume |
function* |
Create generator function |
yield |
Pause generator and produce a value |
| Prototype | Object used for shared behavior/inheritance |
| Prototype Chain | Property lookup chain |
| Inheritance | Reuse parent functionality |
extends |
Create child class |
super() |
Call parent constructor |
super.method() |
Call parent method |
| Method Overriding | Replace inherited behavior |
instanceof |
Check prototype relationship |
Advanced JavaScript builds on several important ideas.
Classes
↓
Create Structured Objects
Iterators
↓
Traverse Data
Generators
↓
Pause & Resume
↓
Generate Values When Needed
Prototypes
↓
Share Behavior
↓
Prototype Chain
Inheritance
↓
Reuse Parent Behavior
The most important relationship to understand is:
Classes
↓
Cleaner Syntax
↓
Built On
↓
Prototypes
And:
Inheritance
↓
extends
↓
Prototype Chain
For iteration:
Iterable
↓
Symbol.iterator
↓
Iterator
↓
next()
↓
{ value, done }
Generators make this process easier:
function*
↓
yield
↓
Pause
↓
next()
↓
Resume
Together, these concepts explain important parts of how JavaScript works internally:
Objects
↓
Prototypes
↓
Classes
↓
Inheritance
Collections
↓
Iterables
↓
Iterators
↓
Generators
Understanding them gives you a stronger foundation for reading advanced JavaScript, working with libraries and frameworks, and understanding the language beyond basic syntax.