Before you begin
Why this matters
Most code that changes an object calls a method on it. In TypeScript, the object a method works on is decided by each call, not by where the method is written. So the same method can work, crash, or change the wrong object, and knowing the rule lets you predict which.
What this helps you reason about
- Predict which object a method changes from the way it is called.
- Spot a method passed on its own that has lost its object, and keep the object with an arrow function.
- Tell a method that needs an object from one that belongs to the class itself.
A hospital ward has a bed board: each ward shows how many beds are empty. A program for it keeps one object per ward and a method admit that takes one bed. This lesson follows how admit knows which ward to change, and how it can lose that ward.
Already know this? The short version
- A method is a function stored once on the class; north.admit() runs it with this set to north, the receiver.
- this is decided by the call, not by where the method is written: obj.m() gives obj, and a method called as m() alone gets undefined (class code is strict), so this.x throws a TypeError.
- To pass a method on, keep its object: () => north.admit(), north.admit.bind(north), or an arrow function field, admit = () => { … }, which gives each object its own function.
- A static method or property belongs to the class and is used as Ward.opened; it exists once, and inside a static method this is the class.
Words in this lesson (6)
- class
- A declaration that describes a kind of object: its properties and a constructor. new Employee(…) creates an object of the class Employee.
- method
- A function declared inside a class, such as admit() { … }. It is called on an object, as in north.admit().
- receiver
- The value a method is called on. In north.admit(), north is the receiver.
- 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.
- 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.
- static member
- A method or property declared with static, such as static opened = 0. It belongs to the class, is used as Ward.opened, and exists once for the whole program.
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 / Receiver
How does a method get the value it works on?
A method is a function declared inside a class. It is called on an object, as in north.admit("Ada"). The object before the dot is the method’s receiver: the value the method works on. Inside the method, the receiver is named this.
One admit method serves every Ward. When north.admit runs, this is north; when south.admit runs, this is south. So this.emptyBeds is north’s count in the first call and south’s count in the second.
class Ward {
constructor(
public name: string,
public emptyBeds: number,
) {}
admit(patient: string): void {
this.emptyBeds = this.emptyBeds - 1;
}
}
const north = new Ward("North", 3);
north.admit("Ada"); // this is north A method receives the object before the dot as this.
Coming from Python: this is like self, but you never write it as a parameter: TypeScript supplies it from the call.
Precisely: where a method is stored
The method is stored once, on the class’s prototype: an object that every Ward is linked to. north.admit finds the function there. The function belongs to no single ward, so the receiver has to come from the call.
Experiment
Follow a method call to its object
north.admit("Ada") finds admit in the class and runs it with this set to north. The same function runs for south, with this set to south.
Why it matters The object before the dot is the one a method works on. Read it from the call, not from the method.
Predict both lines. Then change south.admit("Ben") to north.admit("Ben") and predict again.
class Ward {
constructor(
public name: string,
public emptyBeds: number,
) {}
admit(patient: string): void {
this.emptyBeds = this.emptyBeds - 1;
console.log(
patient + " to " + this.name + ": " + this.emptyBeds + " empty",
);
}
}
const north = new Ward("North", 3);
const south = new Ward("South", 5);
north.admit("Ada");
south.admit("Ben");
What to look for
Before the calls, the stepper enters the constructor once for each new Ward. Step into north.admit("Ada"). The new frame is named admit and lists this first, {name: "North", emptyBeds: 3}, then patient "Ada". At the next stop, this and north both show emptyBeds: 2 while south still shows 5. Node stops on the console.log line twice while it works through the line. The run prints Ada to North: 2 empty, then Ben to South: 4 empty. After the edit, both calls change north, and the second line is Ben to North: 1 empty.
Expected output
Ada to North: 2 empty
Ben to South: 4 empty02 / Copy or original
Does a method change the original value or a copy?
this is a reference to the object, like any variable that holds an object (see Arguments, values, and references). So a method that assigns this.emptyBeds changes the receiver itself, and every name for that object sees the change. A TypeScript method never works on a copy of its object.
Inside a method, every property is reached through this. emptyBeds alone is not the property; TypeScript reports Cannot find name 'emptyBeds'. Did you mean the instance member 'this.emptyBeds'?
const board = north;
board.admit("Ben"); // this is the object north refers to
console.log(north.emptyBeds); // 1 A method changes the object itself, because this is a reference to it.
Precisely: methods that return a new value
A method can also leave this unchanged and return a new object. String methods such as toUpperCase() always work that way, because a string cannot change.
03 / Passed on
What does a method keep when it is passed as a value?
this is decided by each call, not by where the method is written. north.admit, without parentheses, is only the function. If it is passed to another function and called there as action(), there is no object before the dot, and this is undefined. Reading this.emptyBeds then stops the program with TypeError: Cannot read properties of undefined (reading 'emptyBeds').
There are three usual ways to keep the object with the method. Pass an arrow function that makes the call: () => north.admit(). Pass north.admit.bind(north): bind returns a new function whose this is always north.
An arrow function has no this of its own: inside it, this means what it means in the code around it. That gives the third way: declare the method in the class as an arrow function field, admit = () => { … }. Each Ward then gets its own function, made when the Ward is created, and its this is always that Ward.
class Ward {
emptyBeds = 3;
admit = (): void => {
this.emptyBeds = this.emptyBeds - 1;
};
}
const north = new Ward();
const action = north.admit;
action(); // still north: emptyBeds is 2 An ordinary method passed on its own loses its object; keep the object with an arrow function or bind.
Coming from Python: north.admit in Python is a bound method that remembers north; in TypeScript it is the bare function.
Coming from Java: A Java method reference north::admit keeps north. In TypeScript, north.admit does not.
Precisely: this comes from the call
The rule looks only at the call. If a method is stored as a property of another object, as in const button = { press: north.admit }, then button.press() runs admit with this set to button, not north. An arrow function field costs one function per object instead of one shared function on the prototype.
Experiment
Pass a method to another function
action() has no object before the dot, so this is undefined inside admit, and reading this.emptyBeds throws a TypeError. The arrow function makes the call north.admit() itself, with north before the dot.
Why it matters When a method will be called later by other code, pass an arrow function or a bound function, not obj.method alone.
Predict both lines. Then change twice(north.admit) to twice(() => north.admit()) and predict again.
class Ward {
emptyBeds = 3;
admit(): void {
this.emptyBeds = this.emptyBeds - 1;
}
}
function twice(action: () => void): void {
action();
action();
}
const north = new Ward();
try {
twice(north.admit);
} catch (error) {
console.log(String(error));
}
console.log(north.emptyBeds);
What to look for
try and catch let the run continue after an error and print its message. Step into twice. Its frame shows action as function admit(): the function alone, with no ward. Step into action(): the frame is named admit, and it lists no this, because this is undefined. The next stop is already in the catch block, where error shows TypeError: Cannot read properties of undefined (reading 'emptyBeds'). The run prints TypeError: Cannot read properties of undefined (reading 'emptyBeds'), then 3: north never changed. After the edit, action shows arrow function (). Each action() call enters a frame named arrow function (line 16), then admit, where this shows the same object as north. north shows emptyBeds: 2, then 1. The run prints 1. After the edit the panel lists north in every frame, because the arrow function uses north.
Expected output
TypeError: Cannot read properties of undefined (reading 'emptyBeds')
304 / No receiver
Which functions have no receiver?
A static method belongs to the class, not to an object. It is declared with static and called on the class name, as in Ward.withBeds(4). It has no ward to work on: inside it, this is the class itself. A factory function (see Constructors and creating values) is often a static method.
A static property also belongs to the class. There is one Ward.opened for the whole program, however many wards exist. A method that writes Ward.opened changes that one shared value, not its own object.
class Ward {
static opened = 0;
constructor(public emptyBeds: number) {
Ward.opened = Ward.opened + 1;
}
static withBeds(beds: number): Ward {
return new Ward(beds);
}
}
const east = Ward.withBeds(4);
console.log(Ward.opened, east.emptyBeds); // 1 4 A static member belongs to the class: there is one for the whole program, and no object is its receiver.
05 / Try it
Can you explain a fresh example?
Decide which object is this in each call. The next lesson, Stack and heap, shows where values such as these wards are stored while a program runs. Its link is at the end of the page.
class Ward {
constructor(public emptyBeds: number) {}
admit(): void {
this.emptyBeds = this.emptyBeds - 1;
}
}
const west = new Ward(4);
const board = { emptyBeds: 10, admit: west.admit };
board.admit();
west.admit();
console.log(west.emptyBeds, board.emptyBeds);
What is printed?
Reveal the reasoning
3 9. board.admit() calls admit with board before the dot, so this is board, and board’s own emptyBeds goes from 10 to 9. Only west.admit() has west as its receiver, so west goes from 4 to 3.
Practice tickets
- METHOD-101 · Naps never reach the day’s sleep total → Free
- METHOD-102 · The trainers’ session counts are wrong after the booking run → Sign in · Pro
- METHOD-103 · Previewing an eye test fills the optician’s diary → Sign in · Pro