The this keyword
Learn how JavaScript decides this from the call site: default, implicit, explicit, and new binding, their precedence, and strict-mode edge cases.
- 01Read the call siteFind the expression that decides
this, not the place where the function was written. - 02Name the winning rulePredict default, implicit, explicit, bound, and new binding, including precedence.
- 03Handle strict-mode edgesExplain why plain calls become
undefinedin strict mode and how primitives behave.
The call-site idea
this is JavaScript’s way for a regular function to ask, “Who am I working for in this call?” You already saw the friendly version in Methods & this: in user.sayHi(), this is usually the object before the dot. This lesson turns that intuition into the complete set of rules.
The key phrase is call site. Do not start by looking where the function was written. Start where it is called. The same function can have a different this five lines later if the call shape changes.
For a regular function, this is chosen by the call site: default, implicit, explicit, or new binding, with a fixed precedence when rules compete.
this is like the word “my”If Ada says “my laptop,” my means Ada’s laptop. If Grace says the exact same sentence, my means Grace’s laptop. The sentence did not change; the speaker did. this is that kind of word. Its meaning comes from who is “speaking” at the moment the function is called.
- In real life: “My notebook” depends on who says it
- In JavaScript:
this.labeldepends on who called the function - In real life: A speaker at a microphone
- In JavaScript: The call site gives a function its receiver
- In real life: Moving the sentence to another room does not change “my”
- In JavaScript: Where a regular function is written does not choose
this
Where the analogy stops: The analogy stops with arrow functions: arrows do not take the microphone at all. They inherit this from the surrounding code, which is the next lesson.
We will use regular functions throughout. Arrow functions are special: they have no own this, so the four rules below do not apply to them. Keep that thought for Arrow functions & lexical this.
Call-site rules: read the call, not the function
INTERACTIVEA call site is the exact expression that invokes the function: fn(), obj.fn(), fn.call(obj), or new Fn(). JavaScript looks at that expression and chooses this before the function body runs.
Try the trainer. Each row uses real JavaScript. The source uses inline methods or module-level objects so production minification cannot accidentally detach a method while shrinking the code.
Pick a call shape. Then step once to identify the call site, and again to see the this value that real JavaScript used.
script
const ada = { label: "Ada", whoAmI() { return this.label; },};Notice two details professionals rely on. First, building.room.whoAmI() uses building.room as this, not building. Second, once you store a method in a plain variable and call fn(), the dot is gone. That is a new call site.
const ada = { label: "Ada", whoAmI() { return this.label; },};ada.whoAmI();- Returned
- Ada
- this label
- Ada
- Why
- The call site is `ada.whoAmI()`. The object immediately before the dot, `ada`, becomes `this`.
Real JavaScript returned Ada. The rule is implicit binding, so this is Ada.
Default, implicit, explicit & new binding
RULE TABLEThe four regular-function rules are easier to remember if you imagine four ways someone can be “speaking.” Nobody in particular speaks in a plain call. An object speaks in a method call. call, apply, and bind hand the microphone to a chosen value. new builds a brand-new speaker.
| Rule | How to recognize it | What this becomes | Strict-mode note |
|---|---|---|---|
| Default | A plain call like fn() | undefined in strict code; globalThis in sloppy scripts | Strict mode changes this rule the most |
| Implicit | A property call like obj.fn() | The object immediately before the dot/brackets | Same in strict and sloppy mode |
| Explicit | fn.call(obj), fn.apply(obj), or a bound function | The value supplied explicitly; bound functions remember it | Strict mode keeps primitive receivers primitive |
| new | new Fn() | A fresh object linked to Fn.prototype | Constructors run with a new this; classes are strict |
Think of the function as a script to read aloud. The call site tells us who holds the microphone. A plain call has no speaker. A method call gives it to the object. call and friends hand it over explicitly. new creates a new speaker before the script starts.
- In real life: No one is holding it
- In JavaScript: Default binding:
fn() - In real life: The owner picks it up
- In JavaScript: Implicit binding:
obj.fn() - In real life: Someone hands it to Grace
- In JavaScript: Explicit binding:
fn.call(grace) - In real life: A new speaker is built for the speech
- In JavaScript: New binding:
new Fn()
Where the analogy stops: Real microphones do not have a strict-mode switch, and they cannot box primitives. JavaScript can, so use the table for the exact edge cases.
whoAmI()ada.whoAmI()team.lead.whoAmI()whoAmI.call(grace)bound.call(other)new Reporter()new BoundReporter()const fn = ada.whoAmI; fn()
Sort each call shape into the rule that chooses this.
Explicit binding is big enough to get the next lesson, call, apply & bind. For now, know the family: call invokes with individual arguments, apply invokes with an array of arguments, and bind returns a new function that remembers its receiver.
Binding precedence: when rules compete
STEP THROUGHMost call sites match one rule. A few deliberately combine them. Then JavaScript uses precedence: new beats bound, bound beats later call/apply, call/apply beats implicit, implicit beats default.
When call-site rules compete, use this order: new, then bound, then call/apply, then implicit, then default.
script
function show() { "use strict"; return this.label;}function Reporter() { this.label = "new reporter";}Reporter.prototype.show = show; const ada = { label: "Ada", show };const grace = { label: "Grace" };const bound = show.bind({ label: "Bound target" });const BoundReporter = Reporter.bind({ label: "Bound target" }); bound.call(grace);new BoundReporter().show();Read the three results as a ladder. ada.show.call(grace) starts with a method lookup, but call(grace) wins. bound.call(grace) tries to replace a bound receiver, but bound functions keep the earlier value. new BoundReporter() still constructs a fresh object; new wins for constructor calls.
Ask the strongest question first: “Is there new?” Then “Is it already bound?” Then “Did this call use call or apply?” Then “Is there an object before the dot?” If all answers are no, default binding is left.
this in strict mode
REAL OUTPUTStrict mode does not rewrite the whole this system. It changes two important edges: default binding and primitive receivers. In strict mode a plain call gets undefined; a sloppy classic script substitutes globalThis. In strict mode, call(5) keeps the primitive number; sloppy mode boxes it as a Number object.
Strict mode does not change implicit, explicit, or new binding. It changes default binding and primitive receiver boxing.
script
function strictPlain() { "use strict"; return this;}function sloppyPlain() { return this;}function strictNumber() { "use strict"; return typeof this;}function sloppyNumber() { return typeof this;} console.log(sloppyPlain() === globalThis);console.log(strictNumber.call(5));console.log(sloppyNumber.call(5));<!doctype html><meta charset="utf-8"><script>function plain() { return this === globalThis; }function primitive() { return typeof this; }parent.postMessage({ kind: "this-keyword-sloppy", plain: plain(), primitive: primitive.call(5) }, "*");</script>Waiting for the sandboxed classic script…
Waiting for the sandboxed classic script…
| Call | Strict mode | Sloppy script |
|---|---|---|
plain() | this is undefined | this is globalThis |
plain.call(null) | this is null | this becomes globalThis |
plain.call(5) | this is the number primitive | this is a boxed Number object |
| Module or class body | Always strict | Not applicable inside modules/classes |
This site’s components are ES modules, and modules are always strict. Class bodies are always strict too. That is why sloppy default binding is shown as a recorded and tested classic-script fact, not as code the lesson module can directly run during render.
Where you’ll use this
You will meet this in object APIs, classes, array methods, event handlers, and library utilities. Here are the patterns to keep nearby.
const user = { label: "Ada", getLabel() { return this.label; },};const getLabel = user.getLabel;console.log(getLabel.call(user));The method was detached into getLabel. Calling getLabel() would be default binding. Calling getLabel.call(user) borrows the method with the right receiver. You will go deeper on this in Losing this.
const cartLike = { 0: "book", 1: "pen", length: 2 };const joined = Array.prototype.join.call(cartLike, " + ");console.log(joined);Method borrowing is practical: array methods can work on array-like objects when their this has numeric keys and a length. Some callbacks also accept a thisArg, such as array.forEach(fn, thisArg). Browser event listeners have their own rule: a regular function listener usually receives the element as this. The arrow-function lesson explains why arrows ignore that and keep lexical this instead.
Common misconceptions
- “
thismeans the function’s object.” Only at an implicit call site likeobj.fn(). Detached calls lose that object. - “Nested objects make the outer object
this.” The receiver is the object immediately before the final property access. - “
bindcan be overridden withcall.” A bound function remembers its receiver; latercallcannot change it. - “Strict mode breaks methods.” Strict mode changes plain calls and primitive boxing. Normal method calls still use the object before the dot.
- “Arrow functions follow these four rules.” Arrows have no own
this; they close over the surrounding one.
Practice exercises
5 EXERCISESType the two printed lines in order.
"use strict";
function show() { return this?.label ?? String(this); }
const ada = { label: "Ada", show };
const fn = ada.show;
console.log(ada.show());
console.log(fn());ada.show() uses implicit binding, so this.label is Ada. fn() is a plain strict call, so this is undefined and the fallback prints undefined.
Change the call so the detached method still reads from user.
const user = {
label: "Ada",
getLabel() { return this.label; },
};
const getLabel = user.getLabel;
console.log(getLabel.call(user));const user = {
label: "Ada",
getLabel() { return this.label; },
};
const getLabel = user.getLabel;
console.log(getLabel.call(user));call(user) supplies the receiver explicitly, so the method reads user.label.
Use call to join an array-like object.
const cartLike = { 0: "book", 1: "pen", length: 2 };
const joined = Array.prototype.join.call(cartLike, " + ");
console.log(joined);const cartLike = { 0: "book", 1: "pen", length: 2 };
const joined = Array.prototype.join.call(cartLike, " + ");
console.log(joined);join does not require a real array here. It needs a receiver with 0, 1, and length.
Which label wins when a bound constructor is called with new?
function Reporter() { this.label = "new reporter"; }
Reporter.prototype.show = function show() { return this.label; };
const BoundReporter = Reporter.bind({ label: "bound target" });
console.log(new BoundReporter().show());new BoundReporter() creates a fresh object. Reporter writes label onto that object, so show() returns new reporter.
Type the two booleans printed by the comparisons.
function strictPlain() { "use strict"; return this; }
function sloppyPlain() { return this; }
console.log(strictPlain() === undefined);
console.log(sloppyPlain() === globalThis);The strict plain call returns undefined. The sloppy plain call receives globalThis. Both comparisons are true in the tested script.
Check your understanding
7 QUESTIONSQuestion 1 of 7What decides the value of
thisfor a regular function call?Choose an answer to see the explanation.
Question 2 of 7What does the detached strict call print?
Read the code, then predict"use strict"; function show() { return this?.label ?? String(this); } const ada = { label: "Ada", show }; const fn = ada.show; console.log(fn());Choose an answer to see the explanation.
Question 3 of 7Which rule applies to
profile.name.toUpperCase.call(other)for thetoUpperCasecall?Choose an answer to see the explanation.
Question 4 of 7What does the bound constructor print?
Read the code, then predictfunction Reporter() { this.label = "new reporter"; } Reporter.prototype.show = function show() { return this.label; }; const BoundReporter = Reporter.bind({ label: "bound target" }); console.log(new BoundReporter().show());Choose an answer to see the explanation.
Question 5 of 7In strict mode, what is
thisinside a plain function callfn()?Choose an answer to see the explanation.
Question 6 of 7What does this print?
Read the code, then predictfunction show() { "use strict"; return typeof this; } console.log(show.call(5));Choose an answer to see the explanation.
Question 7 of 7Which precedence order is correct, from strongest to weakest?
Choose an answer to see the explanation.
Key takeaways
- Regular-function
thisis decided at the call site. - The four rules are default, implicit, explicit, and
newbinding. - Precedence is
new, bound, call/apply, implicit, then default. - Strict mode makes plain-call
thisbeundefinedand keeps primitive receivers primitive. - Arrow functions are different: they have lexical
this.
Final definition: this is the receiver a regular function gets from how it is called, unless an arrow function inherited it earlier.
Up next: call, apply & bind.