Topic 2.6
this in Functions
In one line
In JavaScript, this depends on how a function is called, not where it is written. TypeScript lets you declare what this must be with a this parameter, and warns when it would be lost.
Think of it like this
The word "I". If you say "I am hungry", it means you. If your friend says the same sentence, "I" means your friend. The sentence did not change; the speaker did. **this is a function's "I":** it means whoever called the function, and that can change from call to call.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- this
- A special value inside a function that depends on how the function was called.
- this parameter
- A fake first parameter,
this: Type, that exists only for the type checker and is erased at runtime. - Method
- A function stored in an object's property and called like
object.method(). - Detached method
- A method copied out of its object into a variable, so there is nothing before the dot when you call it.
- noImplicitThis
- A strict option that reports an error when
thiswould silently beany. - bind
- A function method that returns a copy of the function with a fixed
this. - Arrow function
- A function written
() => {}that has nothisof its own and uses the one around it. - Strict mode
- A JavaScript mode (always on in modules) where an unbound
thisisundefinedinstead of the global object.
Step by step
01this depends on the call
person.describe() has this set to person. const f = person.describe; f() has no object, so this is undefined. The function is the same; the call is different.
02Declare what this must be
Add this: Person as the first parameter. It doesn't take an argument: callers still pass only greeting.
Method calls on an object with the right shape are accepted. A bare call is rejected.
type Person = { name: string };
function describe(this: Person, greeting: string) {
return greeting + ", " + this.name;
}03Let TypeScript tell you about a missing this
In a function with no declared this, writing this.name is an error under noImplicitThis. Declare it, or turn the function into a method.
04The detached method problem, and the fix
An object method knows its this only at a method call. Copy it into a variable and the link is gone, but TypeScript is silent unless the method declares a this type.
With inc(this: Counter) the copy is caught at compile time. bind is the runtime fix.
05Arrow functions keep the surrounding this
Inside a method, use an arrow function for callbacks. It borrows the method's this. A regular function callback has its own this and loses the object.
Try it yourself
- 1
Pass the wrong this
In the first example change
describe.call({ name: "Ravi" }, "Hello")todescribe.call({ nick: "Ravi" }, "Hello"). Predict whether tsc accepts it. The object has noname, socallrejects it withTS2353(an unknown property in an object literal);strictBindCallApplymakescallcheck its first argument against thethistype. - 2
Turn the arrow into a function
In the last example change
startArrowto usefunction (n) { return this.seconds + n; }. Predict the error, then change it back. Why does the arrow version have none? - 3
Remove the this parameter
In the third example remove
this: Counterfrom the type. Predict whether the detached call is still caught. It isn't: you are back to the silent crash from the second example.
Code & diagrams
person.describe works because the thing before the dot has a name. .call lets you choose this, and it is type-checked. The bare calls have no object, so tsc rejects them, and at runtime the first one crashes (the second never runs).
Console
(nothing was printed)Expected console output
Hi, Asha
Hello, Ravi
Uncaught TypeError: Cannot read properties of undefined (reading 'name')No type errors at all, and the program crashes. TypeScript knows this inside inc is the object, but it does not notice that you called it without the object.
Console
(nothing was printed)Expected console output
Uncaught TypeError: Cannot read properties of undefined (reading 'count')Now the detached call is a compile-time error. bound works: bind fixes this, and strictBindCallApply gives bound the right type. The last line still crashes at runtime, which is exactly what the error predicted.
Console
(nothing was printed)Expected console output
1
2
Uncaught TypeError: Cannot read properties of undefined (reading 'count')The arrow in startArrow borrows the method's this. The regular function in startOld gets its own this, and noImplicitThis reports it. (startOld is never called, because it would crash.)
Console
(nothing was printed)Expected console output
[ 1, 2 ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use this in a plain function
At the bottom of the first example add three lines: function whoAmI() {, return this.name;, }.
Break #2
Call a this-typed function without an object
Call describe("Hey") for function describe(this: Person, greeting: string).
Myth vs fact
Myth
this always means the object where the function is written.
Fact
For regular functions it means whatever is before the dot at the call. For arrow functions it means the this of the code around where they were created.
Myth
A this parameter is a real parameter I must pass.
Fact
It exists only for the type checker and is erased. Callers never pass it.
Myth
TypeScript will always catch a lost this.
Fact
Only when the function declares its this type. A method in an object literal or class that you detach is silent, as the second example showed.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
strictBindCallApply(part ofstrict) givesbind,callandapplyprecise types usingCallableFunctioninlib.es5.d.ts, sodescribe.call({ nick: "x" }, "Hi")is checked against thethisparameter. Without it they are loosely typed and accept anything. - ▸
The
ThisParameterType<T>andOmitThisParameter<T>utility types (inlib.es5.d.ts) extract and remove thethisparameter of a function type.ThisType<T>is a marker used inside object literals to setthisfor methods. Typings for options-object APIs (the Vue options API is the well-known one) use it so that methods inside the object see the rightthis. - ▸
DOM types declare
thisexplicitly. Inlib.dom.d.ts,addEventListener's listener is typed(this: HTMLElement, ev: ...) => anyandWindow.ondevicemotionis(this: Window, ev: ...) => any, so a regular-function handler gets the rightthiswithout annotations (arrow functions still ignore it). - ▸
Class methods used as callbacks lose
thisjust like object methods. Arrow-function class fields (handle = () => {...}) capturethisper instance at the cost of one function object per instance and of not being on the prototype. Pick per case.
Remember this
- 1
The problem this solves:
thischanges depending on how a function is called, so a method taken out of its object can crash with no warning in plain JavaScript. Typingthisturns that into an error you see before running. In JavaScript, a regular function'sthisis set at the moment it is called. Inperson.describe(),thisisperson(the thing before the dot). If you take the function out and call it alone (const f = person.describe; f()), there is nothing before the dot, andthisisundefined(in modules and strict mode). The JavaScript course explains the full rules. This topic covers how TypeScript helps. - 2
A **
thisparameter** is a fake first parameter that only exists for the type checker:function describe(this: Person, greeting: string). It is erased when the code is compiled, so callers never pass it. It states "this function must be called as a method of something that looks likePerson". Calls likeperson.describe("Hi")are checked against it, and a baredescribe("Hey")isTS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'Person'.voidthere means "no object before the dot". - 3
With
noImplicitThis(part ofstrict), usingthisin a function that has no declaredthistype is an error:TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. Without it,thiswould silently beanyand nothing would be checked. Either give the function athisparameter, or move the code into a method of an object or class where TypeScript can tell whatthisis. - 4
Methods in object literals and classes get their
thistype automatically: inconst counter = { count: 0, inc() { this.count++ } }, TypeScript knowsthisis the object, sothis.countis a number. But the checker only knows this when the method is called as a method; it does not follow the method when you copy it to another variable.const detached = counter.inc; detached()type-checks fine and then crashes at runtime withCannot read properties of undefined (reading 'count'). That is a real hole in TypeScript. - 5
You can close the hole by giving the method a
thisparameter:inc(this: Counter). Thencounter.inc()is accepted, andconst detached = counter.inc; detached()is rejected withTS2684..bind(counter)returns a new function withthisfixed, and TypeScript understands it (strictBindCallApply, part ofstrict, typesbind,callandapply). - 6
**Arrow functions have no
thisof their own.** They use thethisof the code around them, fixed at the moment they are created. That makes them the right choice for callbacks inside methods:[1, 2].map((n) => this.seconds + n)works because the arrow reuses the method'sthis. A regularfunction (n) { ... }callback gets its ownthis(not the object), which TypeScript reports undernoImplicitThis. This is why arrows are used for event handlers and callbacks in classes (and why class methods passed as handlers lose theirthis, a problem React's class components made famous).
Explain it without notes
How is this decided for a regular function versus an arrow function?
What is a this parameter, and what happens to it when the code is compiled?
Why does const f = obj.method; f() often crash, and how can TypeScript help?
What does noImplicitThis do?
Practice
Write function area(this: { width: number; height: number }): number that returns this.width * this.height. Call it through an object that has those fields and then with .call. What error does a plain area() call give?
Fix the crash: const logger = { prefix: "[app]", log(msg: string) { console.log(this.prefix + " " + msg); } }; const f = logger.log; f("hi");. Give two different fixes.
Inside an object method, use forEach to log each item of this.items with a prefix from this.prefix. Which kind of function must the callback be?
Trade-offs
- ↔
Arrow-function class fields fix lost-
thisbugs by binding per instance, but cost an extra function object per instance, and you can't override them with normal inheritance rules. Prototype methods with explicitbindor wrappers are cheaper but easier to get wrong. - ↔
A
thisparameter makes misuse a compile-time error, but it ties the function to one shape of object. If the function doesn't really need an object, take the data as a normal parameter instead.
Done when you can
I can explain how
thisis decided for regular and arrow functions.I can write a
thisparameter and explain that it disappears when compiled.I can explain and fix
TS2683andTS2684.I can explain how a method loses its
thisand name three fixes.I can use an arrow function in a method callback to keep
this.