JavaScript / ES6+ Bootcamp – Lesson 6 – this, call, apply, bind, Arrow Functions and Closures

This is one of the most important JavaScript lessons for us as a Ruby/Rails developer.

After this lesson, we should be able to read code like:

const user = {
name: "John",
greet() {
console.log(this.name);
}
};

and understand exactly what this means.

We’ll also understand why:

const greet = user.greet;

can behave differently from:

user.greet();

Then we’ll connect that to:

useEffect(() => {
...
});

and eventually to closures, which are fundamental to React and Node.js.


1. What is this?

The easiest starting point is:

this is a special value available inside a function.

But unlike Ruby’s self, its value is usually determined by how a regular function is called.

Consider:

const user = {
name: "John",
greet() {
console.log(this.name);
}
};
user.greet();

Output:

John

Why?

Because we called:

user.greet();

The object before the . is the receiver.

So:

user.greet()
↓
this = user

Therefore:

this.name

is effectively:

user.name

2. Think about the call site

This is the most useful mental model:

user.greet();

Look at the left side of the dot:

user . greet()
^^^^
receiver

For a normal method call, that receiver becomes this.

Example:

const person = {
name: "Alice",
sayName() {
console.log(this.name);
}
};
person.sayName();

Here:

this → person

Output:

Alice

3. But this is not permanently attached to the function

This is where JavaScript differs from the simple mental model we may have from Ruby.

Take:

const user = {
name: "John",
greet() {
console.log(this.name);
}
};

Now:

user.greet();

gives:

John

But:

const greet = user.greet;
greet();

is a different call.

We did:

const greet = user.greet;

So greet now refers to the function.

But we’re no longer calling it as:

user.greet()

We’re calling:

greet()

There is no receiver.

So the this behavior is different.


4. The three things we should distinguish

When we see a function, distinguish:

user.greet
user.greet()
const fn = user.greet;
fn();

They mean different things.

user.greet

Get the function.

user.greet()

Call the function with user as the method receiver.

fn()

Call the function independently.

This distinction is extremely important in JavaScript.


5. this in a regular function

Consider:

function showThis() {
console.log(this);
}

What this is depends on how we invoke the function.

That’s the core idea:

regular function
↓
how was it called?
↓
determines `this`

There are several invocation patterns, and we’ll learn the important ones.


6. Method call

The easiest:

const user = {
name: "John",
greet() {
console.log(this.name);
}
};
user.greet();

Here:

this = user

7. Explicitly set this

JavaScript gives us:

call()
apply()
bind()

These let us control what this refers to.

This is a very common int. topic.


8. call

Suppose:

function greet() {
console.log(this.name);
}

We have an object:

const user = {
name: "John"
};

We can explicitly say:

greet.call(user);

Now:

this = user

Therefore:

John

So:

greet.call(user);

means approximately:

Call greet now, and make this equal to user.


9. call with arguments

Suppose:

function greet(message) {
console.log(`${message}, ${this.name}`);
}

Then:

const user = {
name: "John"
};

Call:

greet.call(user, "Hello");

Output:

Hello, John

The structure is:

function.call(thisArg, arg1, arg2, ...)

For example:

greet.call(user, "Hello");

means:

this = user
message = "Hello"

10. apply

apply is similar to call.

greet.apply(user, ["Hello"]);

Output:

Hello, John

The difference is mainly how arguments are supplied.

call

greet.call(user, "Hello", "How are you?");

apply

greet.apply(user, ["Hello", "How are you?"]);

So:

call → arguments individually
apply → arguments as an array

Historically apply was particularly useful when we already had an array of arguments. With modern JavaScript, spread syntax has reduced some of those use cases.


11. bind

bind is different.

const boundGreet = greet.bind(user);

This does not immediately call greet.

Instead it creates a new function.

Think:

greet.bind(user)
↓
new function
↓
this permanently set to user

Then:

boundGreet("Hello");

Output:

Hello, John

So:

call → call now
apply → call now
bind → create another function

That’s the key distinction.


12. call vs apply vs bind

Memorize this table:

MethodExecutes immediately?Arguments
callYesIndividual
applyYesArray
bindNoReturns new function

Example:

fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
const newFn = fn.bind(obj);
newFn(1, 2);

13. A practical example

Suppose:

const user1 = {
name: "John"
};
const user2 = {
name: "Jane"
};
function greet(message) {
console.log(`${message}, ${this.name}`);
}

Now:

greet.call(user1, "Hello");

Output:

Hello, John

And:

greet.call(user2, "Hello");

Output:

Hello, Jane

Same function.

Different this.

This demonstrates why this in regular functions is associated with the invocation.


14. Arrow functions change the rules

Now comes a critical concept.

Arrow functions do not get their own this.

They use this from the surrounding lexical scope.

Example:

const user = {
name: "John",
greet: () => {
console.log(this.name);
}
};

We might think:

user.greet()
↓
this = user

But that’s not how an arrow function works.

An arrow function does not create its own this.

It captures this from the surrounding scope.


15. Why this matters

Compare:

Regular function

const user = {
name: "John",
greet() {
console.log(this.name);
}
};
user.greet();

Here:

this = user

Arrow function

const user = {
name: "John",
greet: () => {
console.log(this.name);
}
};

Here this does not become user.

This is why object methods generally should not be written as arrow functions when they need dynamic this.


16. The classic callback problem

Now we get to a very common JavaScript issue.

Suppose:

const user = {
name: "John",
greet() {
setTimeout(function() {
console.log(this.name);
}, 1000);
}
};
user.greet();

We might expect:

John

But the inner regular function has its own this behavior.

The setTimeout callback isn’t being called as:

user.someMethod()

So it doesn’t automatically inherit the outer method’s this.


17. Arrow functions solve this nicely

Now:

const user = {
name: "John",
greet() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
};
user.greet();

Now the arrow function:

() => {
console.log(this.name);
}

doesn’t create a new this.

It captures the this from greet().

So:

user.greet()
↓
this = user
↓
arrow callback
↓
uses surrounding this
↓
this = user

Output:

John

This is one of the biggest reasons arrow functions are so useful.


18. Arrow functions and React

We’ll often see:

<button onClick={() => handleClick()}>
Click
</button>

Here the arrow function is primarily being used as a callback.

React will call the arrow function later when the event occurs.

Similarly:

useEffect(() => {
loadUsers();
}, []);

The callback passed to useEffect is an arrow function.

Again, remember:

() => {
...
}

is just a function.

React receives that function and decides when to invoke it.


19. Now: closures

This is perhaps even more important than this.

A closure happens when a function remembers variables from its surrounding lexical scope.

Consider:

function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}

Call:

const counter = createCounter();

Now:

counter(); // 1
counter(); // 2
counter(); // 3

The amazing part is:

createCounter() has already finished executing.

Yet the returned function can still access:

count

Why?

Because the function closed over the variable.


20. Visualize the closure

When we execute:

const counter = createCounter();

think:

createCounter()
|
| creates
v
count = 0
|
| returns function
v
counter ────────→ function
|
└── remembers count

Then:

counter();

the function can still access the remembered count.

After:

counter();

the value becomes:

count = 1

Next:

counter();

the function still has access to the same variable:

count = 2

21. Closure does NOT mean copying the value

This is an important detail.

Some beginners imagine:

count = 0

gets copied into the function.

That’s not the best mental model.

Instead, the function retains access to the lexical environment containing count.

Therefore:

counter();
counter();
counter();

all operate on the same captured variable.


22. Why closures are useful

Closures allow us to keep private state.

Example:

function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
},
getBalance() {
return balance;
}
};
}

Now:

const account = createBankAccount(1000);
account.deposit(500);
console.log(account.getBalance());

Result:

1500

But:

account.balance

doesn’t exist as a public property.

The state is captured by the closure.

This is conceptually similar to encapsulation.


23. Closures and callbacks

Callbacks frequently create closures.

Example:

Output:

Hello John

The returned callback remembers:

name = "John"

even after greetUser() finished.

So:

outer function
↓
creates variable
↓
creates callback
↓
callback remembers variable

That’s a closure.


24. Closures in loops

Here’s a classic int. example:

for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}

Many people expect:

0
1
2

but with var, all callbacks refer to the same function-scoped i, which has reached 3 when the callbacks run.

So we get:

3
3
3

This is a classic closure + asynchronous execution int. question.

Now change var to let:

for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}

Now the callbacks see the per-iteration i values:

0
1
2

This connects directly to our Lesson 2 discussion of let and block/iteration scoping.


25. Closure + React

Closures are everywhere in React.

For example:

function User({ user }) {
const handleClick = () => {
console.log(user.name);
};
return (
<button onClick={handleClick}>
Click
</button>
);
}

The function:

handleClick

uses:

user

from its surrounding scope.

So it forms a closure over user.

This is normal JavaScript behavior, not React-specific magic.


26. Closure + useEffect

Consider:

function UserComponent({ userId }) {
useEffect(() => {
console.log(userId);
}, [userId]);
return null;
}

The callback passed to useEffect accesses:

userId

from the surrounding function scope.

That’s a closure.

This is why closures and React hooks are closely related.

Later, when we study the dependency array deeply, this will become very important.


27. Closure + Node.js

Closures are equally useful in Node.

For example:

function createLogger(prefix) {
return message => {
console.log(`[${prefix}] ${message}`);
};
}
const errorLogger = createLogger("ERROR");
const infoLogger = createLogger("INFO");
errorLogger("Database failed");
infoLogger("Server started");

Output:

[ERROR] Database failed
[INFO] Server started

Each returned function remembers its own prefix.

So closures are not merely “React stuff.”

They’re a fundamental JavaScript feature.


28. this vs closure

These are different concepts.

this

Answers:

What object/context does this regular function call use as this?

Closure

Answers:

What variables from surrounding lexical scopes can this function still access?

For example:

const user = {
name: "John",
greet() {
const message = "Hello";
setTimeout(() => {
console.log(this.name);
console.log(message);
}, 1000);
}
};

The arrow callback has access to:

this
message

but for different reasons:

this → inherited from surrounding context
message → closure over surrounding lexical variable

This distinction is worth remembering.


29. Ruby comparison

Ruby has:

self

JavaScript has:

this

But don’t assume:

Ruby self == JavaScript this

They have important differences.

Ruby self is much more naturally tied to the current execution context/object.

JavaScript this for a regular function is heavily dependent on how the function is called.

And JavaScript arrow functions don’t create their own this.

That distinction is especially important when moving from Rails backend code to React/Node.


30. A very useful mental model

For regular functions, ask:

How was this function called?

For arrow functions, ask:

What is the surrounding `this`?

For closures, ask:

Which variables from outer scopes does this function use?

These three questions will solve a huge number of JavaScript puzzles.


🎯 Interview questions

Try these without running them.

Q1

What is this here?

const user = {
name: "John",
greet() {
console.log(this.name);
}
};
user.greet();

Q2

What’s the problem here?

const greet = user.greet;
greet();

Q3

What’s the difference between:

greet.call(user);

and:

const fn = greet.bind(user);

Q4

What is the difference between call and apply?

Q5

What is a closure?

Q6

Why does this work?

function createCounter() {
let count = 0;
return () => ++count;
}
const counter = createCounter();
counter(); // ?
counter(); // ?

Q7

Why does var produce the classic loop problem?

for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}

🧪 Live coding practice

Problem 1 – this

Predict the output:

const user = {
name: "John",
greet() {
return `Hello ${this.name}`;
}
};
console.log(user.greet());

Problem 2 – call

Write:

function introduce(role) {
console.log(`${this.name} is a ${role}`);
}

Then use call so that:

const user = {
name: "Abhilash"
};

produces:

Abhilash is a Senior Software Engineer

Problem 3 – bind

Given:

const user = {
name: "John"
};
function greet(message) {
console.log(`${message}, ${this.name}`);
}

Create:

const boundGreet = ...

so that:

boundGreet("Hello");

produces:

Hello, John

Problem 4 – Closure

Implement:

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

The count variable should not be directly accessible from outside.


Problem 5 – Closure with argument

Create:

const double = multiplier(2);
double(5); // 10
double(10); // 20

The function multiplier(2) should return another function.

This is an excellent exercise for understanding closures.


Problem 6 – Callback + closure

Implement:

function createGreeter(name) {
return function() {
console.log(`Hello ${name}`);
};
}

Then:

const greetJohn = createGreeter("John");
const greetJane = createGreeter("Jane");
greetJohn();
greetJane();

Expected:

Hello John
Hello Jane

Be able to explain why each returned function remembers a different name.


Final mental model

We now have four layers:

FUNCTION
↓
Function can be passed around as a value
CALLBACK
↓
A function passed to another function
THIS
↓
For regular functions, determined by how the function is called
CLOSURE
↓
A function retains access to variables from its lexical scope

And the most important distinctions:

user.greet
↓
function reference
user.greet()
↓
function call
↓
this = user
greet.call(user)
↓
call now with explicit this
greet.apply(user, args)
↓
call now with explicit this + array arguments
greet.bind(user)
↓
return a new function with bound this

For arrow functions:

arrow function
↓
doesn't create its own `this`
↓
captures surrounding `this`

For closures:

outer scope
↓
function created inside it
↓
inner function remembers outer variables

The connection to React

We can now look at:

function User({ user }) {
const handleClick = () => {
console.log(user.name);
};
return (
<button onClick={handleClick}>
Click
</button>
);
}

and identify:

{ user } → object destructuring
handleClick → function
() => → arrow function
onClick={handleClick}→ function reference
user.name → closure over user

That’s the exact JavaScript foundation we want before going further into React.

Next lesson: JavaScript asynchronous programming – synchronous execution, call stack, event loop, Web/Node APIs, callback queue, microtask queue, Promises, and why async/await works. This will be especially important for us because it connects JavaScript directly to Node.js.

Unknown's avatar

Author: Abhilash

Hi, I’m Abhilash! A seasoned web developer with 15 years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, Vue and React. Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!

Leave a comment