On this page · sections
JavaScript Functions & this
Can you predict
thisfrom the call site — and know when an arrow function makesbindineffective?
Functions are first-class values. A normal function does not permanently receive this from where it was defined. For ordinary functions, this is determined primarily by how the function is called.
Arrow functions are the important exception:
An arrow function does not have its own
this; it capturesthislexically from its surrounding scope.
This distinction explains many frontend bugs involving:
- event handlers
- callbacks
- timers
- class methods
call/apply/bind- constructors
- React handlers
- extracting methods from objects
The senior-level mental model is:
Normal function
↓
Look at the call site
↓
Determine this
Arrow function
↓
Ignore the call site's this
↓
Use surrounding lexical thisQuestions
Function Types
this
Senior-Level Follow-ups
Function Types
1. Declaration vs Expression vs Arrow
Why This Matters
Function syntax affects:
- hoisting
- whether the function has its own
this - whether it can be used with
new - whether it has its own
arguments - whether it has a
prototypeproperty
Problem
What is the difference between these three forms?
function declared() {}
const expressed = function () {};
const arrow = () => {};Comparison
| Kind | Callable before declaration line? | Own dynamic this? | Constructable with new? | Own arguments? | Own prototype? |
|---|---|---|---|---|---|
| Function declaration | Yes | Yes | Yes | Yes | Yes |
| Function expression | No | Yes | Yes | Yes | Yes |
| Arrow function | No | No — lexical | No | No | No |
“Callable before the line” is deliberately different from saying “hoisted.”
For example:
declared();
// works
function declared() {
return "ok";
}But:
expressed();
// ReferenceError
const expressed = function () {};and:
arrow();
// ReferenceError
const arrow = () => {};The bindings for const exist before initialization, but they remain in the Temporal Dead Zone.
this Example
const obj = {
value: 42,
method() {
return this.value;
},
arrow: () => this.value,
};
obj.method();
// 42
obj.arrow();
// depends on the surrounding lexical thisThe important point is that method gets this === obj because it is called as:
obj.method();The arrow does not.
Its this was determined by the surrounding scope where the arrow was created.
In an ES module, top-level this is undefined, so:
const obj = {
arrow: () => this,
};
obj.arrow();
// undefinedThis is one reason arrow functions are generally inappropriate when you intentionally need a dynamically selected receiver.
Interview Answer
“Function declarations are hoisted and callable before their declaration. Function expressions and arrows are initialized where their binding is assigned. Ordinary functions have dynamic
this,arguments, and can be constructable. Arrows have lexicalthis, no ownarguments, and cannot be used withnew. I use arrows when I want lexicalthis, and ordinary methods/functions when I need call-site-dependentthis.”
this
2. How this Is Determined
Why This Matters
The safest way to answer a this question is not:
“What scope is this function in?”
Instead ask:
“How is this function being called?”
For ordinary functions, use this decision process.
Problem
What is this for each of these calls?
function show() {
return this;
}
const obj = { show };
show();
obj.show();
show.call(obj);
new show();The Practical Priority List
For an ordinary function:
1. new
↓
new instance
2. call / apply / bind
↓
explicitly supplied this
3. obj.fn()
↓
obj
4. fn()
↓
undefined in strict mode
global object in sloppy modeArrow functions are outside this decision tree:
arrow()
↓
lexical this from surrounding scopeThey do not acquire a new this from the call site.
Rule 1: new
function User(name) {
this.name = name;
}
const user = new User("Ada");
user.name;
// "Ada"When a constructable function is called with new, JavaScript creates a new object and invokes the constructor with that object as this.
Rule 2: Explicit Binding
function show() {
return this.name;
}
const user = {
name: "Ada",
};
show.call(user);
// "Ada"Similarly:
show.apply(user);
// "Ada"and:
const boundShow = show.bind(user);
boundShow();
// "Ada"Rule 3: Method Call
const user = {
name: "Ada",
show() {
return this.name;
},
};
user.show();
// "Ada"The relevant part is the call expression:
user.show()not merely the fact that show happens to be stored inside user.
Rule 4: Bare Function Call
function show() {
return this;
}
show();In strict mode:
undefinedIn a sloppy-mode script, the value is the global object.
ES modules are always strict, so module code gets:
undefinedfor a bare call of an ordinary function.
The Classic Detached Method
This distinction is critical:
const user = {
name: "Ada",
show() {
return this.name;
},
};
user.show();
// "Ada"But:
const fn = user.show;
fn();The function has been extracted from the object.
It is now a bare call:
user.show()
↓
this = user
fn()
↓
this = undefined in strict modeThe function itself did not change.
The call site changed.
Interview Answer
“For an ordinary function, I inspect the call site.
newcreates the constructor receiver, explicitcall/apply/bindsuppliesthis,obj.fn()usesobj, and a bare call givesundefinedin strict mode or the global object in sloppy mode. Arrow functions are different: they capturethislexically.”
3. call, apply, and bind
Why This Matters
These methods let you explicitly control this for ordinary functions.
The key difference is whether the function runs immediately.
Problem
What is the difference between call, apply, and bind?
Example
function greet(greeting, punctuation) {
return `${greeting} ${this.name}${punctuation}`;
}
const user = {
name: "Ada",
};call
Arguments are passed individually:
greet.call(user, "Hello", "!");
// "Hello Ada!"The function executes immediately.
apply
Arguments are supplied as an array-like collection:
greet.apply(user, ["Hello", "!"]);
// "Hello Ada!"It also executes immediately.
bind
bind does not execute the function.
It creates a new function with a permanently associated this value for ordinary calls.
const boundGreet = greet.bind(user, "Hello");
boundGreet("!");
// "Hello Ada!"You can also partially apply arguments:
const sayHello = greet.bind(user, "Hello");
sayHello("!");
// "Hello Ada!"Conceptually:
call
└── invoke now
└── explicit this
apply
└── invoke now
└── explicit this + argument list
bind
└── create new function
├── preset this
└── optionally preset argumentsbind Is Not Just About this
This:
const fn = greet.bind(user, "Hello");has two effects:
this → user
first argument → "Hello"This makes bind useful for both:
- preserving a method receiver
- partial application
Constructor Behavior
Bound functions have an important special case.
If a bound function is used with new, the bound thisArg is ignored:
function User(name) {
this.name = name;
}
const BoundUser = User.bind(
{ ignored: true },
"Ada"
);
const user = new BoundUser();
user.name;
// "Ada"The this used by the constructor is the newly created instance, not:
{ ignored: true }The bound argument "Ada" is still supplied.
Interview Answer
“
callandapplyinvoke immediately with an explicitthis; their main difference is how arguments are supplied.bindcreates a new function with presetthisand optionally preset leading arguments. If that bound function is called withnew, the boundthisArgis ignored and the newly constructed instance becomesthis.”
Senior-Level Follow-ups
4. Arrows and thisArg
Why This Matters
Many array APIs accept a thisArg:
array.map(callback, thisArg)That does not mean it can control this for an arrow function.
Ordinary Function
const values = [1, 2, 3];
const result = values.map(
function (n) {
return n * this.multiplier;
},
{ multiplier: 10 }
);
console.log(result);
// [10, 20, 30]The callback is an ordinary function, so map can provide the thisArg.
Arrow Function
const values = [1, 2, 3];
const result = values.map(
(n) => n * this.multiplier,
{ multiplier: 10 }
);The thisArg is ignored for purposes of the arrow’s this.
The arrow gets this from its surrounding lexical environment.
Why bind Does Not Fix an Arrow
This does not work:
const arrow = () => this.name;
const bound = arrow.bind({
name: "Ada",
});
bound();
// still uses the arrow's lexical thisbind can create a bound function, but it cannot replace the lexical this captured by an arrow.
This is a useful interview distinction:
Ordinary function
↓
dynamic this
↓
call/apply/bind can influence it
Arrow function
↓
lexical this
↓
call/apply/bind cannot replace itInterview Answer
“
thisArgworks with callbacks that have dynamicthis. It doesn’t change an arrow’s lexicalthis. Likewise,bindcannot rebind an arrow’sthis, because the arrow never has its own dynamically boundthisin the first place.”
5. new vs Bound Functions
Why This Matters
This is a common senior-level follow-up because it tests whether you know that constructor calls have special this semantics.
Consider:
function User(name) {
this.name = name;
}
const BoundUser = User.bind(
{ ignored: true },
"Ada"
);
const user = new BoundUser();
console.log(user.name);
// "Ada"The bound object is not used as the constructor’s this.
Instead:
new BoundUser()
↓
create new instance
↓
constructor executes
↓
this → new instanceThe bound argument is still preserved:
BoundUser
├── bound thisArg → ignored under new
└── bound arguments → ["Ada"]Therefore:
user.name;
// "Ada"instanceof
The bound function remains associated with its target constructor for instanceof purposes:
user instanceof User;
// trueThis works even though BoundUser itself does not provide a normal own prototype for instances to inherit from.
Interview Answer
“A bound constructor preserves its target function and partially applied arguments. When invoked with
new, its boundthisArgis ignored becausenewcreates the receiver. The resulting object is still an instance associated with the original constructor.”
6. Detached Methods in Callbacks
Why This Matters
One of the most common real-world this bugs is passing a method as a callback.
Consider:
const obj = {
value: 42,
handle() {
console.log(this.value);
},
};
button.addEventListener("click", obj.handle);The callback is passed as a function value.
It is not equivalent to:
button.addEventListener("click", () => {
obj.handle();
});The first version gives the DOM event system control over the callback’s invocation.
For an ordinary function listener, addEventListener invokes the listener with this set to the EventTarget on which the listener was registered.
So in this case:
obj.handledoes not receive:
this === objFix 1: Wrapper Function
button.addEventListener("click", () => {
obj.handle();
});Now the wrapper’s job is simply to perform:
obj.handle()which provides obj as the receiver.
Fix 2: Bind the Method
button.addEventListener(
"click",
obj.handle.bind(obj)
);bind creates a new function whose ordinary-call this is fixed to obj.
One practical consequence:
button.removeEventListener(
"click",
obj.handle.bind(obj)
);does not remove the listener added earlier, because each bind() call creates a different function object.
Store the bound function if you need to remove it:
const boundHandle = obj.handle.bind(obj);
button.addEventListener("click", boundHandle);
// later
button.removeEventListener("click", boundHandle);Class Methods
Prototype methods can also lose their receiver:
class View {
update() {
console.log(this);
}
handle() {
this.update();
}
}
const view = new View();
button.addEventListener("click", view.handle);handle is extracted from view.
The callback is therefore no longer invoked as:
view.handle()If handle needs the instance, bind it:
class View {
constructor() {
this.handle = this.handle.bind(this);
}
handle() {
this.update();
}
update() {}
}Class Fields With Arrow Functions
Another approach is an arrow function class field:
class View {
handle = () => {
this.update();
};
update() {}
}The arrow captures the instance’s lexical this, so extracting it does not lose the receiver:
const view = new View();
button.addEventListener("click", view.handle);The trade-off is that the function is an own property of each instance, rather than a shared prototype method.
Conceptually:
Prototype method
View.prototype.handle
↑
shared by instances
Arrow class field
instance #1 → own handle function
instance #2 → own handle function
instance #3 → own handle functionFor most UI components this distinction is rarely the dominant performance concern, but it is worth understanding when designing large object graphs or performance-sensitive classes.
React Connection
In React function components, event handlers are normally written as arrows or function expressions that close over render-local values:
function Button({ user }) {
const handleClick = () => {
console.log(user.name);
};
return <button onClick={handleClick}>Open</button>;
}Here, this is generally not the issue.
React function components do not rely on class-style dynamic this for handlers.
The important mechanism is the closure:
render
↓
user binding
↓
handleClick closureThis is why modern React code generally prefers closures and lexical variables over manually managing this.
Class components are different:
class Button extends React.Component {
handleClick() {
console.log(this.props.user);
}
}Passing:
<button onClick={this.handleClick} />can lose the instance receiver unless the method has been bound or defined as an arrow class field.
Interview Answer
“Passing
obj.handleas a callback extracts the method from its receiver. The callback is no longer invoked asobj.handle(), so the original receiver isn’t automatically preserved. I can preserve it with a wrapper,bind, or an arrow class field. In modern React function components, closures largely replace the need for class-stylethismanagement.”
Final Interview Strategy
When you see a this question, don’t guess from the surrounding code.
Use this checklist:
1. Is it an arrow or ordinary function?
↓
2. If ordinary, how is it being called?
↓
3. Is there new?
↓
4. Is there call/apply/bind?
↓
5. Is it obj.fn()?
↓
6. Otherwise, is it a bare call?
↓
7. If it's an arrow, find its lexical surrounding thisThe most important distinction is:
Ordinary function
→ this is determined dynamically
Arrow function
→ this is captured lexicallyAnd the most common production mistake is:
const fn = obj.method;
fn();because extracting a method changes the call site.
Quick Reference
| Situation | this for ordinary function |
|---|---|
new Fn() | New instance |
fn.call(obj) | obj |
fn.apply(obj) | obj |
fn.bind(obj)() | obj |
obj.fn() | obj |
fn() in strict mode | undefined |
fn() in sloppy mode | Global object |
| Arrow function | Lexical this |
Remember:
call site → ordinary this
lexical scope → arrow thisSenior-Level Signal
A weak answer is:
“
thisrefers to the object.”
That’s often wrong.
A stronger answer is:
“For ordinary functions,
thisis determined by the call site. A method call supplies the receiver, explicit binding can supply it directly,newcreates a new receiver, and a bare call getsundefinedin strict mode.”
A senior answer continues:
“Arrow functions deliberately don’t participate in dynamic
this; they capture it lexically. That’s whycall,apply,bind, and array-methodthisArgcannot replace an arrow’sthis. In production, the important failure mode is losing a method’s receiver when passing it as a callback. I choose between a wrapper,bind, or an arrow class field based on the required lifecycle and ownership semantics.”
The key habit is simple:
Don’t ask “What is
thishere?” Ask “How was this function called?”