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:
thisis 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
greetnow, and makethisequal touser.
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 = usermessage = "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 individuallyapply → 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 nowapply → call nowbind → create another function
That’s the key distinction.
12. call vs apply vs bind
Memorize this table:
| Method | Executes immediately? | Arguments |
|---|---|---|
call | Yes | Individual |
apply | Yes | Array |
bind | No | Returns 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(); // 1counter(); // 2counter(); // 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 vcount = 0 | | returns function vcounter ────────→ 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:
012
but with var, all callbacks refer to the same function-scoped i, which has reached 3 when the callbacks run.
So we get:
333
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:
012
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:
thismessage
but for different reasons:
this → inherited from surrounding contextmessage → 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(); // 1counter(); // 2counter(); // 3
The count variable should not be directly accessible from outside.
Problem 5 – Closure with argument
Create:
const double = multiplier(2);double(5); // 10double(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 JohnHello 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 valueCALLBACK ↓A function passed to another functionTHIS ↓For regular functions, determined by how the function is calledCLOSURE ↓A function retains access to variables from its lexical scope
And the most important distinctions:
user.greet ↓function referenceuser.greet() ↓function call ↓this = usergreet.call(user) ↓call now with explicit thisgreet.apply(user, args) ↓call now with explicit this + array argumentsgreet.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 destructuringhandleClick → function() => → arrow functiononClick={handleClick}→ function referenceuser.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.