Arrow functions & lexical this
Learn why arrow functions inherit this, how that helps callbacks and timers, and why arrows are the wrong shape for object methods and many event handlers.
- 01Explain lexical thisSay why an arrow uses the this of the code around it, from where it was written.
- 02Pick the right callbackUse an arrow for a timer or array callback that needs the this of the method around it.
- 03Avoid the method mistakeWrite object methods with method shorthand, and read event.currentTarget in event listeners.
Arrows keep this from where they are written
You met arrow functions in the Arrow functions lesson. They are the short (x) => x * 2 way to write a function. This lesson is about one thing arrows do differently: an arrow does not get its own this.
A quick reminder. this is a special word inside a function. It names the object the function should work on. A regular function gets a new this each time it is called, and the value depends on how you call it. For example, room.show() gives show the object room as its this.
An arrow never gets a new this. It uses the this of the code around it, at the place where the arrow is written.
This is called lexical this. "Lexical" means "decided by where the code is written". You met the same idea in the Scope and Closures lessons: a function finds a variable by looking outward from where it is written. An arrow finds this the same way.
An arrow function uses the this of the code around it. call, apply, and bind can't change it.
const user = { name: "Asha" };
const readName = () => user.name;
user.name = "Ravi";
console.log(readName());Line 1 makes a user object. Line 2 makes an arrow that reads user.name. Line 3 changes the name to Ravi. Line 4 calls the arrow, and it prints Ravi. The arrow keeps a link to the user object, not a copy of the old name. A kept this works the same way: the arrow keeps the object itself, so it sees later changes to it.
You write a sticky note inside a room. It says "this room". You hand the note to someone in the hallway. The note does not change to mean "this hallway". It still means the room where you wrote it. An arrow's this works like that note.
- In real life: The room where you write the note
- In JavaScript: The code around the arrow, and its
this - In real life: A note that says "this room"
- In JavaScript: An arrow that uses
this - In real life: Handing the note to someone in the hallway
- In JavaScript: Passing the arrow to a timer, a callback, or
call - In real life: The note still means the room where it was written
- In JavaScript: The arrow still uses the
thisfrom where it was written
This makes arrows a good fit for callbacks and timers. A callback is a function you hand to other code, which calls it later. That code often calls it with no object before the dot, so a regular function would get no useful this.
It also makes arrows a bad fit for object methods, and for event listeners that need the clicked element as this. By the end of this lesson, you will know which kind of function to write before you start typing.
An arrow ignores how it is called
STEP THROUGHThe this keyword lesson showed how a regular function gets this from the call. The call site is the place in the code where a function is called. Two examples:
obj.fn(): the object before the dot,obj, becomesthis.fn.call(other):callrunsfnand setsthistoother.
An arrow doesn't look at the call site. Its this is fixed when the arrow is made, by the code around it. The next example makes an arrow inside an object method. Then it uses call to try to give the arrow a different this. Guess what line 10 prints before you step through.
Guess whether line 10 prints studio or garage, then step through.
script
"use strict"; name: "studio", makeReader() { return () => this.name; }}; const reader = room.makeReader();console.log(reader.call({ name: "garage" }));Line 10 is the surprise. reader.call({ name: "garage" }) looks like it should set this to the garage object. That works for a regular function. But reader is an arrow. It was made on line 5, while room.makeReader() was running, and inside that method this was room. So the arrow keeps reading room.name and prints studio.
reader was made inside room.makeReader(), where this was room. It keeps that this. So reader.call(...) can't give it the garage object, and line 10 prints studio.One visitor asks at the front desk every time: "Which company am I visiting?" That is a regular function. Another visitor has a badge that was printed before the visit. That is an arrow. Sending the second visitor to a different desk doesn't print a new badge.
- In real life: A visitor who asks at the front desk each time, "Which company am I visiting?"
- In JavaScript: A regular function gets
thisfrom each call - In real life: A visitor whose badge was printed before the visit
- In JavaScript: An arrow's
this, fixed when the arrow is made - In real life: Sending the visitor to a different desk
- In JavaScript: Calling the arrow with
call,apply, orbind
You can call bind on an arrow. It makes a new function, but it can't set this. It can still fill in the first arguments ahead of time, so the new function needs fewer arguments when you call it.
const label = (prefix, name) => prefix + name;
const greet = label.bind({ ignored: true }, "Hello, ");
console.log(greet("Asha"));Line 2 passes { ignored: true } as the this value, and the arrow ignores it. bind also passes "Hello, ", which fills the first parameter, prefix. So line 3 calls the arrow with prefix set to "Hello, " and name set to "Asha". It prints Hello, Asha.
Arrows borrow more than this
LANGUAGE RULEEvery regular function gets a few special names of its own, made new on each call. this is one of them. Here are the others:
arguments: a list-like object that holds every value passed to the function.super: inside a class, a way to reach the parent class's methods.new.target: the class or function thatnewwas used with.
An arrow gets none of these. Just like with this, it uses the ones from the function around it.
function printNames() { const readCount = () => arguments.length; console.log(readCount());}printNames("Asha", "Ravi");Line 2 makes an arrow inside printNames. The arrow reads arguments.length. The arrow has no arguments of its own, so this is the arguments of printNames. Line 5 passes two values, so line 3 prints 2. This can be handy in a tiny helper. But when you write the function yourself, named parameters are clearer than arguments.
The same is true for super and new.target. An arrow written inside a class constructor uses the constructor's ones. A constructor is the function that runs when you make an object with new.
"use strict";function Parent() {}Parent.prototype.label = function () { return "parent";}; class Child extends Parent { constructor(first, second) { super(); const readOuterBindings = () => [arguments.length, super.label(), new.target.name]; console.log(readOuterBindings().join("|")); }} new Child("tea", "toast");Line 15 runs new Child("tea", "toast"). Inside the constructor, line 10 makes an arrow. The arrow reads three things from the constructor:
arguments.lengthis2, for the two values passed in.super.label()calls the parent'slabelmethod, which returnsparent.new.target.nameisChild, the class thatnewwas used with.
Line 11 joins the three with | and prints 2|parent|Child.
const makeUser = () => ({ name: "Asha" });try { new makeUser();} catch (error) { console.log(error.name);}Line 3 tries new makeUser(). That throws an error, so line 5 prints the error's name: TypeError.
An arrow can read variables from the code around it. But it has no this, arguments, super, or new.target of its own. It also can't make objects: using new with an arrow throws a TypeError.
Arrows in callbacks and timers
INTERACTIVEA callback often runs later, or inside another function. Timers and array methods call your callback for you. They don't call it as timer.callback() or object.callback(). They call it alone, as callback(), with no object before the dot.
In strict mode, a regular function called alone gets this set to undefined. Strict mode is the safer set of rules that "use strict" turns on at the top of the code.
The next example uses a small function called runLater. It calls the callback right away, so you don't have to wait for a real clock. But it calls it the same way a timer does: alone, as callback(). Switch the callback between a regular function and an arrow, and step through each one.
Switch the callback. A regular function gets this from the call. An arrow keeps the this of start.
script
"use strict";function runLater(callback) { callback();} label: "Focus", seconds: 0, start() { runLater(function () { this.seconds += 1; console.log(this.label + ": " + this.seconds); }); }}; try { timer.start();} catch (error) { console.log(error.name);}With the regular callback: line 18 calls timer.start(), so inside start, this is the timer. But the callback is a separate function. runLater calls it alone, so the callback gets this set to undefined. Line 11 tries to change this.seconds and throws a TypeError.
With the arrow callback: the arrow doesn't get a new this from runLater. It uses the this that start already has, which is the timer. Line 11 adds 1 to seconds, and line 12 prints Focus: 1.
runLater calls the callback alone, as callback(). Left: a regular callback then gets this = undefined, so line 11 throws a TypeError. Right: the arrow was written inside start(), so its this is the timer. It adds 1 to seconds and prints Focus: 1.This is why you often see arrows inside setTimeout, setInterval, map, and forEach. You will also see them in promise callbacks, which come later in the course.
Before arrows existed, people kept this in a callback in other ways. You will still see them in older code. The next example writes the same method three ways. All three print JS-1|JS-2|JS-3:
withArrow(line 6): an arrow callback uses the method'sthis.withSelf(line 9):const self = thissaves the method'sthisin a normal variable. The regular callback readsselfinstead ofthis.withBind(line 17):.bind(this)makes a copy of the regular callback withthisfixed to the method'sthis.
"use strict";const tagger = { prefix: "JS-", ids: [1, 2, 3], withArrow() { return this.ids.map((id) => this.prefix + id); }, withSelf() { const self = this; return this.ids.map(function (id) { return self.prefix + id; }); }, withBind() { return this.ids.map(function (id) { return this.prefix + id; }.bind(this)); }}; console.log(tagger.withArrow().join("|"));console.log(tagger.withSelf().join("|"));console.log(tagger.withBind().join("|"));The arrow version is the shortest and clearest. It says: use the same this as the method I'm inside.
function makeLabel(prefix) {
return [1, 2].map((value) => prefix + value);
}
console.log(makeLabel("#").join(","));An arrow borrows this, but its parameters are still its own. Here map passes each number in as value. prefix comes from makeLabel, the function around the arrow. The output is #1,#2.
An arrow makes a bad object method
COMPAREArrows help when a callback inside a method needs the method's this. They hurt when the arrow itself is the method. A method usually needs the object before the dot to become this. An arrow ignores that object.
"use strict";function makeCounter() { return { count: 0, inc: () => this.count++ };} const counter = makeCounter.call(undefined);try { counter.inc();} catch (error) { console.log(error.name);} const safeCounter = { count: 0, inc() { this.count++; return this.count; }};console.log(safeCounter.inc());Line 9 calls makeCounter with this set to undefined. Line 5 makes the arrow inc inside makeCounter, so the arrow's this is undefined too. Line 11 calls counter.inc(), and the arrow ignores counter before the dot. this.count++ tries to read count from undefined. That throws, so line 13 prints TypeError.
Lines 16 to 22 write the method another way. inc() { ... } is method shorthand: a regular function written right inside the object. Line 23 calls safeCounter.inc(), so this is safeCounter. Line 19 adds 1 to its count, and line 23 prints 1.
inc is an arrow, so it ignores counter before the dot. Its this is undefined, so this.count++ throws a TypeError. Right: inc() is method shorthand, so safeCounter.inc() makes this = safeCounter, and it prints 1.The working object writes its method right inside the object, as inc() { ... }. This course writes every this example that way, so the object before the dot is easy to see. It also stays correct after the site's build tools shrink the code. Those tools can change how a function written somewhere else gets called, and then this is lost.
The table compares the two kinds of function. Read each row as: in this case, what does this become?
| When you write | A regular function | An arrow function |
|---|---|---|
A method call, obj.fn() | this is obj, the object before the dot. | It ignores obj and uses the outer this. |
A call with nothing before it, fn(), in strict mode | this is undefined. | It still uses the outer this. |
| A callback inside a method | It loses the method's this, unless you use bind. | It keeps the method's this. |
| An event listener | The browser sets this to the element, event.currentTarget. | It uses the outer this. Read event.currentTarget instead. |
call, apply, or bind | They can set this. | They can't change this. They can still pass arguments. |
new | It can make an object, if written as a constructor. | It throws a TypeError. Arrows can't make objects. |
There is one place where an arrow method is a good idea: a class field. A class field is a property you write directly in a class body, like count = 0;. Each new object gets its own copy of it.
If the field holds an arrow, like increment = () => ++this.count;, each object gets its own arrow. That arrow is made while the object is being built, so its this is that object. You will see this again in the Losing this lesson and in the Classes lessons. For plain objects written with { }, use method shorthand.
class Counter { count = 0; increment = () => ++this.count;} class ChildCounter extends Counter {}const first = new Counter();const second = new Counter();const child = new ChildCounter();console.log(first.increment === second.increment);const detached = first.increment;console.log(detached());console.log(child.increment() === 1);Here is what each line prints:
- Line 10 prints
false.firstandsecondeach have their ownincrementarrow, so the two are different functions. - Line 12 prints
1. Line 11 saved the arrow indetached, away fromfirst. Called alone, it still usesfirstasthis. - Line 13 prints
true.ChildCounterextendsCounter, sochildgets its own arrow too, and its first call returns1.
A class-field arrow stays tied to its object, so you can pass it around as a callback. But every object gets one more function, which uses more memory.
A normal class method is one function that all the objects share. It uses less memory when you make many objects. But to pass it around, you need bind or a wrapping arrow. Choose based on whether you need to pass the method around, and how many objects you make.
this in event listeners
REAL BUTTONEvents come later in the course. But this in event code is too common to skip. An event listener is a function the browser calls when something happens, like a click. You add one with addEventListener.
When the browser calls a regular function listener, it sets this to the element whose listener is running. The event object holds the same element as event.currentTarget.
An arrow listener doesn't get that this from the browser. It keeps the outer this. In React components and JavaScript modules, the outer this is not the button. Click the real button below and compare the two listeners.
button.addEventListener("click", function (event) { console.log(this === event.currentTarget);}); button.addEventListener("click", (event) => { console.log(this === event.currentTarget); console.log(event.currentTarget.textContent);});- WaitingClick the button to see what both listeners get.
Click the real button, or press Send a click. Two listeners run: a regular function and an arrow. Compare each one's this with event.currentTarget.
The simple rule: when in doubt, use event.currentTarget instead of this. It works in a regular listener and in an arrow listener. It keeps working if someone later changes one kind of listener into the other.
this. An arrow listener keeps the outer this, which is not the button. event.currentTarget is the button in both, so read that instead.button.addEventListener("click", (event) => { event.currentTarget.disabled = true; event.currentTarget.textContent = "Saved";});Lines 2 and 3 change the button through event.currentTarget. They turn the button off and change its text to Saved.
panel.addEventListener("click", (event) => {
console.log(event.target === button);
console.log(event.currentTarget === panel);
});
button.click();This example only runs on a page where panel holds a button. It uses two names from the event object:
event.targetis the element where the click started. Here that is the button.event.currentTargetis the element whose listener is running. Here that is the panel.
Line 5 clicks the button. The click starts at the button, then moves up to the panel, so the panel's listener runs. Line 2 prints true, and line 3 prints true.
Where you will use this
SORTThe choice is not "arrows are new, regular functions are old". The question to ask is: should this function get this from whoever calls it, or keep the this of the code around it? You will ask it when you write click handlers, change lists of data, set timers, write classes, and write tests.
- An object method that reads
this.count - A
forEachcallback inside a method that readsthis.prefix - An event listener that uses
thison purpose as the clicked element - A
mapcallback that only uses its parameter - A class field handler that must stay tied to its object when you pass it around
- A function that will be called with
newIt belongs in Regular function. An arrow can't be used with
new. Trying it throws aTypeError.
Sort each case by the kind of function that gets this right.
A helpful habit: say in one sentence which object should be this.
- "This object should do the work." Use method shorthand or a regular function.
- "This inner callback should use the same object as the method around it." Use an arrow.
- No
thisat all? Use whichever is easiest to read.
- An object method that reads
this: use method shorthand. - A timer or array callback inside a method that needs the method's
this: use an arrow. - A function you call with
new: use a class or a regular function. - An event listener that changes its element: use
event.currentTarget.
Common misconceptions
- "An arrow sets this to itself." No. An arrow has no
thisof its own. It uses the outerthis. - "bind fixes every function."
bindcan setthisfor a regular function. It can't change an arrow'sthis. - "Arrows are always the better callback." They are great when you want the outer
this. But a regular event listener is the right choice when you want the browser to setthisto the element. - "An arrow method uses the object before the dot." An arrow ignores the dot. Use method shorthand for methods that use
this. - "event.target and event.currentTarget are the same." They are the same for a simple button. But the click can start on an element inside. The element whose listener is running is
currentTarget. - "An arrow gets its own arguments object." No. It uses the
argumentsof the regular function around it. When you write the function yourself, use named parameters. - "An arrow gets its own super and new.target." No. It uses the ones from the class method or constructor around it.
- "A class-field arrow is shared by all objects, like a class method." No. Each object gets its own arrow. That uses more memory, but the arrow stays tied to its object.
Practice exercises
5 EXERCISESRead the code. What error name does it print?
"use strict";
function makeCounter() {
return {
count: 0,
inc: () => this.count++
};
}
const counter = makeCounter.call(undefined);
try {
counter.inc();
} catch (error) {
console.log(error.name);
}TypeErrorThe arrow ignores counter before the dot. It uses the this from makeCounter, which is undefined. Reading this.count from undefined throws, so the catch block prints TypeError.
A timer callback written as a regular function would lose this. This version uses an arrow. What does it print?
"use strict";
const timer = {
label: "Tea",
start() {
setTimeout(() => {
console.log(this.label);
}, 0);
}
};
timer.start();"use strict";
const timer = {
label: "Tea",
start() {
setTimeout(() => {
console.log(this.label);
}, 0);
}
};
timer.start();The arrow is written inside start, so it uses the this of start, which is timer. When the timer runs the callback, it reads timer.label and prints Tea.
Replace the regular callback and the self variable with an arrow callback. What do both versions print?
"use strict";
const list = {
prefix: "#",
values: [1, 2, 3],
labels() {
const self = this;
return this.values.map(function (value) {
return self.prefix + value;
});
}
};
console.log(list.labels().join(","));
// Rewrite labels() with an arrow callback."use strict";
const list = {
prefix: "#",
values: [1, 2, 3],
labels() {
return this.values.map((value) => this.prefix + value);
}
};
console.log(list.labels().join(","));The arrow callback uses the same this that labels() got from list.labels(). It prints the same #1,#2,#3 without the self variable.
A cart object has an add method that changes this.total. Which way of writing it is safer?
const cart = {
total: 0,
add(price) {
this.total += price;
}
};Use method shorthand. cart.add(5) should set this to cart. An arrow would ignore cart.
Make this arrow listener turn off the clicked button without using this.
button.addEventListener("click", () => {
this.disabled = true;
});button.addEventListener("click", (event) => {
event.currentTarget.disabled = true;
});An arrow listener should use event.currentTarget, the element whose listener is running. It keeps working even if someone later changes the arrow into a regular function.
Check your understanding
10 QUESTIONSQuestion 1 of 10What does lexical
thismean for an arrow function?Choose an answer to see the explanation.
Question 2 of 10What does this code print when
calltries to give the arrow a newthis?Read the code, then predict"use strict"; const tool = { name: "Brush", show() { const read = () => this.name; console.log(read.call({ name: "Drill" })); } }; tool.show();Choose an answer to see the explanation.
Question 3 of 10Inside a method, a callback needs the method's
this. Which callback is usually best?Choose an answer to see the explanation.
Question 4 of 10What error name does the arrow method print?
Read the code, then predict"use strict"; function makeCounter() { return { count: 0, inc: () => this.count++ }; } const counter = makeCounter.call(undefined); try { counter.inc(); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 5 of 10In the browser, a regular function is passed to
addEventListener. What isthisinside it?Choose an answer to see the explanation.
Question 6 of 10What happens if you use
newwith an arrow function?Choose an answer to see the explanation.
Question 7 of 10In an event listener, when you are not sure, what should you read instead of
this?Choose an answer to see the explanation.
Question 8 of 10An arrow is written inside a class constructor. Which values does it take from the constructor?
Choose an answer to see the explanation.
Question 9 of 10Two objects are made from a class with an arrow in a class field. What is true?
Choose an answer to see the explanation.
Question 10 of 10A panel's listener runs after a click on a button inside the panel. Which value is the panel?
Choose an answer to see the explanation.
Key takeaways
- A regular function gets
thisfrom how it is called. - An arrow function gets
thisfrom where it is written. - Use an arrow for a callback that needs the
thisof the method around it. - Use method shorthand for object methods that read or change
this. - In event listeners, read
event.currentTargetinstead ofthis.
An arrow function has lexical this: it keeps the this of the code around it, instead of getting a new one from each call.
Up next: Losing this.