Before you begin
Why this matters
Code that uses this works or fails depending on how the function is called, not on how it is written. If you know the rule, you can predict which object a function changes, read the compiler’s this errors, and spot a function passed to other code that has lost its object.
What this helps you reason about
- Predict this from the way a function is called: with an object before the dot, alone, or through call, apply, or bind.
- Explain why an arrow function inside a method sees the method’s object and an inner ordinary function does not.
- Choose between a method and an arrow function field when a function will be passed to other code.
Closures and captured variables showed that a function keeps the variables of the code where it is written. The word this does not work like that. In an ordinary function, this is set again by each call. Readers coming from Java, C#, or Python expect this to stay with its object; in JavaScript, which TypeScript runs as, it does not. This lesson follows the booths of a toll bridge. It shows who decides this, how TypeScript checks it, and how arrow functions change the rule.
Already know this? The short version
- An ordinary function gets this from each call: obj.f() gives obj, and f() alone gives undefined in strict mode, so this.x throws TypeError: Cannot read properties of undefined. A method stored in another object gets that object.
- With strict, TypeScript refuses this in a function that does not declare it ('this' implicitly has type 'any'…); a this parameter, function f(this: Booth), makes every call supply a Booth. Methods of objects and classes are not checked when they are passed on alone.
- f.call(obj, a) and f.apply(obj, [a]) set this for one call; f.bind(obj) returns a new function whose this never changes, and each bind makes another function.
- An arrow function has no own this and uses the this around it; call and bind cannot change it. A class method is one shared function on the prototype; an arrow function field is one function per object with this fixed. The section “What goes wrong with this in real programs?” covers handlers, binding in code that runs often, fields and subclasses, and arrow functions in object literals.
Words in this lesson (8)
- this
- Inside a method, the receiver of the current call: the object before the dot. A method called with no object before the dot gets undefined as this.
- receiver
- The value a method is called on. In north.admit(), north is the receiver.
- this parameter
- A first parameter named this with a type, such as function describe(this: Booth). It is not passed by callers and is removed from the JavaScript output; it tells TypeScript which object every call must supply.
- bind
- A method of every function: f.bind(obj) returns a new function that calls f with this set to obj, however the new function is called.
- arrow function
- A function written as a value, without a name, such as () => "stall " + i. The part before => lists its parameters, and the part after it is the value it returns.
- class
- A declaration that describes a kind of object: its properties and a constructor. new Employee(…) creates an object of the class Employee.
- arrow function field
- A field of a class whose value is an arrow function, such as countNow = () => { … }. Each new object gets its own function, whose this is always that object.
- callback
- A function passed to another function, which calls it, for example once for each element of an array.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / The call
What decides this in an ordinary function?
An object can hold functions as well as values. Inside the braces below, pass() { … } is short for a property named pass whose value is a function. A function stored in an object is called a method. In this lesson, an ordinary function is any function that is not an arrow function: a function declaration, a function expression, or a method.
Inside an ordinary function, the word this names an object that the call supplies. When the call is written north.pass(), this is north: the object before the dot, called the receiver. south stores the same function, and the call south.pass() gives it south. The function does not belong to north; it is only stored there.
A call with no object before the dot, such as pass(), supplies no receiver. TypeScript’s output runs in strict mode, a stricter set of JavaScript rules, and in strict mode this is then undefined. Reading this.cars stops the program with TypeError: Cannot read properties of undefined (reading 'cars').
const north = {
name: "North",
cars: 0,
pass() {
this.cars = this.cars + 1;
},
};
const south = { name: "South", cars: 5, pass: north.pass };
north.pass(); // this is north: 1 car
south.pass(); // this is south: 6 cars
const pass = north.pass;
pass(); // this is undefined: TypeError For an ordinary function, this is the object before the dot in the call, and undefined when there is none.
Coming from Python: In Python, north.pass remembers north. In JavaScript, north.pass is the function alone, and the next call supplies the object again.
Coming from Java: A Java method’s this is always the object it belongs to. A JavaScript function that uses this can be stored in, and called through, any object.
Precisely: without strict mode
Outside strict mode, a call with no receiver gets the global object, globalThis, as this, and this.cars reads or creates a property of it with no error. Class bodies and ES modules are always strict, and the strict setting makes TypeScript add "use strict" to every file it outputs. The specification sets this for each call in OrdinaryCallBindThis.
Experiment
Call one function through two objects
south.pass holds the same function as north.pass. Each call supplies its own this: the object before the dot. pass() has no object before the dot, so this is undefined, and reading undefined.cars throws.
Why it matters To know what this is, look at the call, not at the object where the function was written.
Predict both lines. Then replace line 12 with const pass = north.pass; pass(); and predict again.
const north = {
name: "North",
cars: 0,
pass() {
this.cars = this.cars + 1;
console.log(this.name + ": " + this.cars);
},
};
const south = { name: "South", cars: 5, pass: north.pass };
north.pass();
south.pass();
What to look for
Step into north.pass(). The frame is named pass and lists this first: the north object, with cars: 0. After line 5, this and north both show cars: 1, and the run prints North: 1. Step into south.pass(): the frame has the same name, but this is now the south object; south shows cars: 6, north keeps 1, and the run prints South: 6. The stepper sometimes stops on the same line twice; that is normal for this debugger. After the edit, the second pass frame lists no this, because this is undefined; the run prints North: 1, then stops in pass with TypeError: Cannot read properties of undefined (reading 'cars').
Expected output
North: 1
South: 602 / Type checks
How does TypeScript check this?
TypeScript checks this where it can. type Booth = { name: string; cars: number } names an object shape: any object with those two properties. A function that is not written inside an object or a class gives TypeScript no way to know which object a call will supply. With the strict setting, which turns on the check named noImplicitThis, it refuses to guess: this.name in such a function gives 'this' implicitly has type 'any' because it does not have a type annotation.
The fix is a this parameter: a first parameter named this, with a type, such as function describe(this: Booth). It is not a real parameter. Callers do not pass it, and it is removed from the JavaScript output. It tells TypeScript which object each call must supply. { describe } is short for { describe: describe }, so west.describe() supplies west, which has a name and cars. describe() alone supplies nothing, which TypeScript writes as void, and is refused: The 'this' context of type 'void' is not assignable to method's 'this' of type 'Booth'.
The check covers functions that declare this. A method written inside an object or a class has no this parameter, so TypeScript accepts const pass = north.pass; pass(); from the last section with no error.
type Booth = { name: string; cars: number };
function describe(this: Booth): string {
return this.name + " has seen " + this.cars + " cars";
}
const west = { name: "West", cars: 9, describe };
west.describe(); // accepted: this is west
describe(); // compile error: no Booth A this parameter lets TypeScript refuse a call that supplies the wrong object, or none.
Precisely: this: void
A function can declare this: void to promise that it does not use this. A library that calls a callback with no receiver can ask for such a function, and then passing a function that needs a Booth is refused. ThisParameterType<typeof describe> gives Booth, and OmitThisParameter removes the this parameter from a function type.
Experiment
Call a function that declares this
this: Booth tells TypeScript that every call must supply a Booth. west has a name and cars, so west.describe() supplies one. describe() supplies nothing, which TypeScript writes as void, and the compiler refuses the call before the program runs.
Why it matters Give a function that uses this a this parameter, and TypeScript checks every call of it.
Predict the output. Then change line 8 to console.log(describe()); and predict again.
type Booth = { name: string; cars: number };
function describe(this: Booth): string {
return this.name + " has seen " + this.cars + " cars";
}
const west = { name: "West", cars: 9, describe };
console.log(west.describe());
What to look for
Step into west.describe(). The frame is named describe and lists only this, the west object: the this parameter is not a real parameter, so describe has no parameter to list. The value of this comes from the call, west.describe(). The run prints West has seen 9 cars. After the edit, nothing runs: the compiler reports The 'this' context of type 'void' is not assignable to method's 'this' of type 'Booth'. for line 8.
Expected output
West has seen 9 cars03 / call and bind
How can you choose this for a call?
Every function has methods that let you choose this. charge.call(south, 3) calls charge at once, with this set to south and euros set to 3. charge.apply(south, [3]) does the same, with the arguments given as an array.
charge.bind(north) does not call charge. It returns a new function, a bound function, whose this is always north. Calling it later as atNorth(5) gives North. So does atNorth.call(south, 6): a bound function ignores any other this it is offered.
Each call of bind makes a new function, so charge.bind(north) === charge.bind(north) is false. This matters when code must find the same function again, for example to remove a listener; the section on real programs shows it.
type Booth = { name: string };
function charge(this: Booth, euros: number): string {
return this.name + " takes " + euros;
}
const north: Booth = { name: "North" };
const south: Booth = { name: "South" };
console.log(charge.call(south, 3)); // South takes 3
const atNorth = charge.bind(north);
console.log(atNorth(5)); // North takes 5
console.log(atNorth.call(south, 6)); // North takes 6 call and apply choose this for one call; bind returns a new function whose this never changes.
Precisely: what a bound function keeps
A bound function stores the original function, the this value, and any arguments given to bind after it: charge.bind(north, 5)() gives North takes 5. Its name property is "bound charge". When a bound function is called with new, the bound this is ignored and a new object is made.
Experiment
Call a bound function with another object
call sets this for one call. bind made atNorth with this fixed to north. A bound function ignores the this that call offers, and binding it again makes another function that still calls atNorth, with north.
Why it matters Once a function is bound, nothing at the call can change its this.
Predict all three lines, especially the last. Then change line 12 to console.log(atNorth.bind(south)(6)); and predict again.
type Booth = { name: string };
function charge(this: Booth, euros: number): string {
return this.name + " takes " + euros;
}
const north: Booth = { name: "North" };
const south: Booth = { name: "South" };
const atNorth = charge.bind(north);
console.log(charge.call(south, 3));
console.log(atNorth(5));
console.log(atNorth.call(south, 6));
What to look for
The panel shows atNorth as function (), with no name: a bound function. Each call enters a frame named charge, even when it goes through atNorth. The frame lists this and euros: {name: "South"} with 3, then {name: "North"} with 5, then {name: "North"} with 6. The run prints South takes 3, North takes 5, and North takes 6. After the edit, the last line is still North takes 6.
Expected output
South takes 3
North takes 5
North takes 604 / Arrow functions
Why does an arrow function use the this around it?
An arrow function has no this of its own. Inside it, this means what it means in the code around it, in the same way as any other variable from that code (Closures and captured variables). Below, the arrow function line is written inside report, so it uses report’s this. The call north.report() set that to north.
An ordinary function written inside a method gets its own this, from its own call. line(plate) has no object before the dot, so an ordinary line would get undefined, not north. TypeScript refuses it: 'this' implicitly has type 'any' because it does not have a type annotation. Code written before arrow functions existed stored the object first, const self = this;, and used self in the inner function.
Because an arrow function takes this from where it is written, call, apply, and bind cannot change it: line.call(south, "AB 12") still uses north.
const north = {
name: "North",
plates: ["AB 12", "CD 34"],
report() {
const line = (plate: string) => this.name + " saw " + plate;
for (const plate of this.plates) {
console.log(line(plate)); // North saw AB 12, …
}
},
}; An arrow function uses the this of the function it is written in; an ordinary function gets its own this from its own call.
Coming from Java: Inside a Java lambda, this is the enclosing object, as in an arrow function. A JavaScript ordinary function is the exception: it has its own this.
Precisely: this outside every function
An arrow function written outside every function uses the this of the file. That is undefined in an ES module and the exports object in the CommonJS output the stepper runs. In a file with no import or export, TypeScript refuses such a read: The containing arrow function captures the global value of 'this'. An arrow function also has no arguments object and cannot be called with new.
Experiment
Use this from an inner function
line is an arrow function, so it has no this of its own and uses report’s this, which the call north.report() set to north. An ordinary inner function gets its own this from its own call, line(plate), which supplies none.
Why it matters Inside a method, write helpers that need this as arrow functions.
Predict both lines. Then replace line 5 with function line(plate: string) { return this.name + " saw " + plate; } and predict again.
const north = {
name: "North",
plates: ["AB 12", "CD 34"],
report() {
const line = (plate: string) => this.name + " saw " + plate;
for (const plate of this.plates) {
console.log(line(plate));
}
},
};
north.report();
What to look for
Step into north.report(), then into line(plate). The inner frame is named line and lists this, then plate, "AB 12" first and "CD 34" next. Its this is the same North object as in report, although line(plate) has no object before the dot. The run prints North saw AB 12 and North saw CD 34. After the edit, nothing runs: the compiler reports 'this' implicitly has type 'any' because it does not have a type annotation. for line 5.
Expected output
North saw AB 12
North saw CD 3405 / Classes
Should a class method be an arrow function field?
A class describes a kind of object, and new Booth() creates an object of the class Booth. cars = 0 is a field: a property that every new Booth gets when it is created. count(plate) { … } is a method of the class.
A class method is stored once, on the class’s prototype: an object that every Booth is linked to, where north.count finds the function. So all Booths share one count. It follows the rule of the first section. plates.forEach(north.count) passes the function alone: forEach calls it once for each element of plates, with no object before the dot, and this is undefined. A function passed like this, for another function to call, is a callback.
An arrow function field, such as countNow = (plate: string): void => { … }, is a field whose value is an arrow function. It is made again for each new Booth, while that Booth is being created, so its this is always that Booth, and plates.forEach(north.countNow) works. The cost is one function per object instead of one for the whole class.
Two other fixes keep the method. plates.forEach((plate) => north.count(plate)) makes the call with north before the dot. plates.forEach(north.count, north) uses the second argument of forEach, which forEach gives to the callback as this.
class Booth {
cars = 0;
count(plate: string): void {
this.cars = this.cars + 1;
}
countNow = (plate: string): void => {
this.cars = this.cars + 1;
};
}
const north = new Booth();
console.log(north.count === new Booth().count); // true
console.log(north.countNow === new Booth().countNow); // false A class method is one shared function that needs its object in each call; an arrow function field is a new function for each object, with this fixed.
Coming from React: In class components, handleClick = () => { … } fields, and this.handleClick = this.handleClick.bind(this) in the constructor, both solve this problem: React calls the handler with no object before the dot.
Precisely: where a field lives
Field initializers run while the object is created, before the rest of the constructor runs (in a subclass, right after super() returns). So an arrow function field is a property of the object itself: Object.keys(north) gives ["cars", "countNow"], and count is not listed, because it lives on Booth.prototype.
Experiment
Pass a field and a method to forEach
countNow was made for north when north was created, so its this is always north. count is the one shared method; forEach calls it with no object before the dot, so its this is undefined.
Why it matters Before passing a method to other code, check whether it is a method or an arrow function field.
Predict the output. Then change line 15 to plates.forEach(north.count); and predict again.
class Booth {
cars = 0;
count(plate: string): void {
this.cars = this.cars + 1;
}
countNow = (plate: string): void => {
this.cars = this.cars + 1;
};
}
const north = new Booth();
const plates = ["AB 12", "CD 34", "EF 56"];
plates.forEach(north.countNow);
console.log(north.cars);
What to look for
new Booth() on line 13 first passes through frames named <instance_members_initializer> and Booth: the stepper is setting the fields. Then north shows cars: 0 and countNow: arrow function (plate), with no count, because count is stored on the class. forEach calls countNow three times: each call enters a frame named countNow, with this, the north object, and the next plate, and north’s cars goes to 1, 2, and 3. The run prints 3. After the edit, the first call enters count, whose frame lists plate but no this, and the run stops there with TypeError: Cannot read properties of undefined (reading 'cars'). Nothing is printed.
Expected output
306 / In production
What goes wrong with this in real programs?
Each of these bugs is a this decided by a call that your code does not write.
A method passed as a handler. button.addEventListener("click", this.open) passes open alone. A DOM event listener is called with this set to the element, not to your object, so this.count + 1 quietly stores NaN on the element, with no error. A method passed to forEach or to a library gets undefined and throws. Pass () => this.open(), or make open an arrow function field.
A bind or wrapper made in code that runs often. A render function, which draws the screen again after each change, that binds or wraps a method makes a new function on every run. addEventListener ignores a listener that is already registered, but only when it is the same function, so each run adds one more listener. removeEventListener with a fresh bind finds nothing to remove, and the old listeners keep their objects alive, as below. Make the function once, and keep it.
Arrow function fields and subclasses. A subclass, class NightLane extends Lane, cannot turn an arrow function field into a method: Class 'Lane' defines instance member property 'label', but extended class 'NightLane' defines it as instance member function. Nor can it reach the field through super: Class field 'label' defined by the parent class is not accessible in the child class via super. Keep a function that subclasses build on as a method.
An arrow function in an object literal. In { name: "Bay B", text: () => this.name }, this is not the new object but the this of the code around the braces. Use the method form, text() { … }, when the function needs the object it is stored in.
const listener = barrier.onPaid.bind(barrier);
booth.addEventListener("paid", listener);
// a new function: nothing is removed
booth.removeEventListener("paid", barrier.onPaid.bind(barrier));
// the same function: removed
booth.removeEventListener("paid", listener); Make a bound function or wrapper once and keep it; keep methods for subclasses; use the method form for a function that needs its own object.
Precisely: EventTarget
EventTarget is built into browsers and into Node.js. addEventListener(type, f) adds f to a list of listeners for type, removeEventListener(type, f) removes the entry that is the same function f, and dispatchEvent(new Event(type)) calls every listener for type, in the order they were added, with this set to the target.
Experiment
Remove a listener with a fresh bind
barrier.onPaid.bind(barrier) on line 15 makes a new function. removeEventListener looks for that new function in the list of listeners, does not find it, and removes nothing. listener is the function that was added, so removing it works.
Why it matters Keep the function you add as a listener, and remove that same function.
Predict how many lines print. Then change line 15 to booth.removeEventListener("paid", listener); and predict again.
class Barrier {
lifts = 0;
onPaid(): void {
this.lifts = this.lifts + 1;
console.log("lift " + this.lifts);
}
}
const booth = new EventTarget();
const barrier = new Barrier();
const listener = barrier.onPaid.bind(barrier);
booth.addEventListener("paid", listener);
booth.dispatchEvent(new Event("paid"));
booth.removeEventListener("paid", barrier.onPaid.bind(barrier));
booth.dispatchEvent(new Event("paid"));
What to look for
booth shows Node.js’s own fields, written with Symbol(…); the one to watch is Symbol(kEvents), the list of listeners by event name. After line 13 it shows Map(1) {"paid" => Object}, and after line 15 it still does. Each dispatchEvent call enters a frame named onPaid, whose this is the barrier, and barrier shows lifts: 1, then 2. The run prints lift 1 and lift 2: the listener was never removed. After the edit, booth shows Map(0) {} after line 15, the second dispatchEvent calls no listener, and the run prints lift 1 only.
Expected output
lift 1
lift 207 / Try it
Can you predict which object this is?
Follow this through each call: the call’s own object, then the arrow function, then the bound function.
type Lane = { name: string; cars: number };
function show(this: Lane): string {
const next = () => this.cars + 1;
return this.name + " " + next();
}
const day: Lane = { name: "Day", cars: 4 };
const night: Lane = { name: "Night", cars: 1 };
const showDay = show.bind(day);
console.log(show.call(night), showDay.call(night));
What does the program print?
Reveal the reasoning
Night 2 Day 5. show.call(night) runs show with this set to night. next is an arrow function, so it uses show’s this, night, and gives 1 + 1 = 2. showDay is bound to day, so showDay.call(night) ignores night: this is day, and next gives 4 + 1 = 5.
Practice tickets
- TS-401 · The crossing log stops with an error when trains are sent → Free
- TS-402 · Every bay sign shows the station’s name → Sign in · Pro
- TS-403 · Motorway speed signs never flash for a new limit → Sign in · Pro
Keep exploring
Primary references
- ECMAScript specification: OrdinaryCallBindThis ↗
- ECMAScript specification: Function.prototype.bind ↗
- MDN: this ↗
- MDN: Arrow function expressions ↗
- MDN: Function.prototype.bind() ↗
- MDN: EventTarget.removeEventListener() ↗
- TypeScript Handbook: this parameters ↗
- TypeScript Handbook: Classes (this at runtime) ↗
- TypeScript compiler options: noImplicitThis ↗