completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Explain lexical thisSay why an arrow uses the this of the code around it, from where it was written.
  • 02
    Pick the right callbackUse an arrow for a timer or array callback that needs the this of the method around it.
  • 03
    Avoid 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.

The main idea

An arrow function uses the this of the code around it. call, apply, and bind can't change it.

An arrow reads the object when it runsPop out in the code editor (opens in a new tab)JavaScript
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.

Real-life analogyAn arrow carries a sticky note

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 this from 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 THROUGH

The 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, becomes this.
  • fn.call(other): call runs fn and sets this to other.

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.

Can call give an arrow a new this?
Step 0 of 4Ready
Your turn: follow the blue line

Guess whether line 10 prints studio or garage, then step through.

Running in
  1. script
Next: line 2
"use strict";  name: "studio",  makeReader() {    return () => this.name;  }}; const reader = room.makeReader();console.log(reader.call({ name: "garage" }));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

The arrow function in the box reader points to the room cart, which holds name studio. The call reader.call with a garage object does not change that, so it prints studio.name: "studio"roomthisreader.call({ name: "garage" })() => this.namereaderprints studio
The arrow 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.
Real-life analogyA visitor badge printed ahead of time

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 this from 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, or bind
bind still works for arguments

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.

bind can still fill in an arrow's argumentsPop out in the code editor (opens in a new tab)JavaScript
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 RULE

Every 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 that new was used with.

An arrow gets none of these. Just like with this, it uses the ones from the function around it.

An arrow reads the outer function's argumentsPop out in the code editor (opens in a new tab)JavaScript
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.

An arrow borrows arguments, super, and new.targetPop out in the code editor (opens in a new tab)JavaScript
"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.length is 2, for the two values passed in.
  • super.label() calls the parent's label method, which returns parent.
  • new.target.name is Child, the class that new was used with.

Line 11 joins the three with | and prints 2|parent|Child.

An arrow can't be used with newPop out in the code editor (opens in a new tab)JavaScript
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.

What an arrow can't have

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

INTERACTIVE

A 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 timer callback
Step 0 of 4Ready
Your turn: follow the blue line

Switch the callback. A regular function gets this from the call. An arrow keeps the this of start.

Running in
  1. script
Next: line 6
"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);}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the callback you pass to runLater

Changing the callback starts a new run of the real code.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

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.

Left: a regular callback called alone gets this as undefined and throws a TypeError. Right: an arrow callback points to the timer cart as this, seconds becomes 1, and it prints Focus: 1.regular callbackfunctionthis = undefinedTypeErrorarrow callback() =>thisseconds: 1timerFocus: 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's this.
  • withSelf (line 9): const self = this saves the method's this in a normal variable. The regular callback reads self instead of this.
  • withBind (line 17): .bind(this) makes a copy of the regular callback with this fixed to the method's this.
Three ways to keep this in a callbackPop out in the code editor (opens in a new tab)JavaScript
"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.

The arrow still gets its own parametersPop out in the code editor (opens in a new tab)JavaScript
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

COMPARE

Arrows 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.

An arrow method next to method shorthandPop out in the code editor (opens in a new tab)JavaScript
"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.

Left: counter.inc() where inc is an arrow. this is undefined, so it throws a TypeError. Right: safeCounter.inc() where inc is method shorthand. this is safeCounter, so it prints 1.counter.inc()inc: () =>counterthis = undefinedTypeErrorsafeCounter.inc()inc()safeCounterthis = safeCounterprints 1
Left: 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.
Write methods inside the object

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?

How this works in a regular function and in an arrow
When you writeA regular functionAn 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 modethis is undefined.It still uses the outer this.
A callback inside a methodIt loses the method's this, unless you use bind.It keeps the method's this.
An event listenerThe browser sets this to the element, event.currentTarget.It uses the outer this. Read event.currentTarget instead.
call, apply, or bindThey can set this.They can't change this. They can still pass arguments.
newIt 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.

Each object gets its own class-field arrowPop out in the code editor (opens in a new tab)JavaScript
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. first and second each have their own increment arrow, so the two are different functions.
  • Line 12 prints 1. Line 11 saved the arrow in detached, away from first. Called alone, it still uses first as this.
  • Line 13 prints true. ChildCounter extends Counter, so child gets its own arrow too, and its first call returns 1.
Class-field arrow or normal class method?

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 BUTTON

Events 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.

Click a real button and compare this
Two event listenersPop out in the code editor (opens in a new tab)JavaScript
button.addEventListener("click", function (event) {  console.log(this === event.currentTarget);}); button.addEventListener("click", (event) => {  console.log(this === event.currentTarget);  console.log(event.currentTarget.textContent);});
Live button0 listener runs
  • WaitingClick the button to see what both listeners get.
Try it yourself

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 two listeners are added with addEventListener, so the results come from the browser's real event system.

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.

A page with a Save changes button. A regular function listener points to the button, so this is the button. An arrow listener does not, so this is not the button. In both listeners, event.currentTarget is the button.pageSave changesfunctionthis = button() =>this ≠ buttonin both listeners:event.currentTarget = button
The browser gives a regular listener the button as this. An arrow listener keeps the outer this, which is not the button. event.currentTarget is the button in both, so read that instead.
Use currentTarget in event codePop out in the code editor (opens in a new tab)JavaScript
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.

target can be an element insideJavaScript
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.target is the element where the click started. Here that is the button.
  • event.currentTarget is 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

SORT

The 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.

Regular function or arrow here?
  • An object method that reads this.count
  • A forEach callback inside a method that reads this.prefix
  • An event listener that uses this on purpose as the clicked element
  • A map callback 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 new

    It belongs in Regular function. An arrow can't be used with new. Trying it throws a TypeError.

Try it yourself
0 of 6 correct

Sort each case by the kind of function that gets this right.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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 this at all? Use whichever is easiest to read.
A quick checklist
  • 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 this of its own. It uses the outer this.
  • "bind fixes every function." bind can set this for a regular function. It can't change an arrow's this.
  • "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 set this to 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 arguments of 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 EXERCISES
Exercise 1 · Warm-upPredict what an arrow method does

Read the code. What error name does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
function makeCounter() {
  return {
    count: 0,
    inc: () => this.count++
  };
}
const counter = makeCounter.call(undefined);
try {
  counter.inc();
} catch (error) {
  console.log(error.name);
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix a timer callback

    A timer callback written as a regular function would lose this. This version uses an arrow. What does it print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const timer = {
      label: "Tea",
      start() {
        setTimeout(() => {
          console.log(this.label);
        }, 0);
      }
    };
    timer.start();

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeReplace const self = this with an arrow

      Replace the regular callback and the self variable with an arrow callback. What do both versions print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      "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.

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeChoose how to write a method

        A cart object has an add method that changes this.total. Which way of writing it is safer?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFix an event listener

          Make this arrow listener turn off the clicked button without using this.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          button.addEventListener("click", () => {
            this.disabled = true;
          });

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            10 QUESTIONS
            Lesson quiz · 10 questionsScore: first tries count
            1. Question 1 of 10What does lexical this mean for an arrow function?

              Choose an answer to see the explanation.

            2. Question 2 of 10What does this code print when call tries to give the arrow a new this?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "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.

            3. 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.

            4. Question 4 of 10What error name does the arrow method print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "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.

            5. Question 5 of 10In the browser, a regular function is passed to addEventListener. What is this inside it?

              Choose an answer to see the explanation.

            6. Question 6 of 10What happens if you use new with an arrow function?

              Choose an answer to see the explanation.

            7. 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.

            8. 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.

            9. 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.

            10. 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 this from how it is called.
            • An arrow function gets this from where it is written.
            • Use an arrow for a callback that needs the this of the method around it.
            • Use method shorthand for object methods that read or change this.
            • In event listeners, read event.currentTarget instead of this.

            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.

            CompleteFrontend Clear concepts. Working examples.