---
title: 'Context in Arrow Functions'
source: 'https://academia.sh/en/courses/javascript-object-model/context-in-arrow-functions'
course: 'Objects and Functions in JavaScript'
language: en
updated: '2026-08-17T18:09:50+00:00'
license: 'CC BY-SA 4.0'
---

# Context in Arrow Functions

Arrow functions having no context of their own, lexical resolution, the absence of a constructor and prototype, the cost of arrows defined as class fields, and cases of misuse.

In the previous lesson's last example, the third fix for the callback problem was an
arrow function, and it required no binding operation at all. The reason is that an
arrow function **does not take** its `this` value from the call site.

Arrow functions have no `this` binding of their own. The `this` used in their body is
resolved like an ordinary variable: searched from the inside out, through the scope
chain of where it was defined. The lexical scope rule covered in the Programming
Foundations course's Scope and Lifetime lesson applies here to `this` as well. This one
difference is why none of the previous lesson's four rules apply to arrow functions.

## A Function With No Context of Its Own

The shortest way to see the distinction is to place the two definition forms side by
side on the same object.

```js
const recordWithMethod = {
  sensor: "S-01",
  value: 21.4,
  format() {
    return `${this.sensor}: ${this.value}`;
  },
};

const recordWithArrow = {
  sensor: "S-02",
  value: 19.8,
  format: () => {
    return this === undefined ? "this: undefined" : "this: something else";
  },
};

console.log(recordWithMethod.format());
console.log(recordWithArrow.format());

const brokenArrow = recordWithArrow.format;
console.log(brokenArrow());
console.log(recordWithArrow.format.call(recordWithMethod));
```

```
S-01: 21.4
this: undefined
this: undefined
this: undefined
```

An object literal establishes no scope. Because the arrow function is defined in the
module body, its `this` value comes from there, and in the module body this value is
`undefined`. Calling it through the object changes nothing.

The last line settles the rule: even `call` cannot change an arrow function's context.
Because there is no place to bind to, explicit binding silently has no effect — and
throws no error either. `bind` is just as ineffective in the same way; it produces a
new function but does not touch the inner resolution of `this`.

## The Benefit of Lexical Resolution

Having no context of its own looks like a shortcoming; its real value shows up in
callbacks given from inside a method. The method's `this` value is automatically
carried into an arrow function defined inside it.

```js
class MeasurementSeries {
  constructor(sensor, values) {
    this.sensor = sensor;
    this.values = values;
  }

  labeledArrow() {
    return this.values.map((value) => `${this.sensor}:${value}`).join(" ");
  }

  labeledFunctionExpr() {
    return this.values
      .map(function (value) {
        return `${this.sensor}:${value}`;
      })
      .join(" ");
  }

  labeledFallback() {
    const self = this;
    return this.values
      .map(function (value) {
        return `${self.sensor}:${value}`;
      })
      .join(" ");
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8]);

console.log(series.labeledArrow());
try {
  console.log(series.labeledFunctionExpr());
} catch (error) {
  console.log(`function expression: ${error.constructor.name}`);
}
console.log(series.labeledFallback());
```

```
S-01:21.4 S-01:19.8
function expression: TypeError
S-01:21.4 S-01:19.8
```

The function expression in the second method establishes its own `this` binding, and
because it is called as a callback, this binding falls to the default rule; in strict
mode it is `undefined` and the read throws an error. The third method copies the
context into an ordinary variable — this is the common fix from before arrow functions
entered the language, and it still works correctly. The first method does the same job
without an extra name.

The rule can be summarized as: **if the caller provides no context, an arrow function
is the right choice.** Callbacks, functions given to array methods, and helper
functions defined inside a method all fit this description.

## The Other Things That Are Missing

`this` is not the only thing missing from arrow functions. The `arguments` object,
`new.target`, and the `prototype` property are not present either. The last of these
is the direct reason arrow functions cannot be used as constructors.

```js
const arrowCalibrate = (value) => value * 1.02;
function functionCalibrate(value) {
  return value * 1.02;
}

console.log("prototype" in arrowCalibrate);
console.log("prototype" in functionCalibrate);
console.log(typeof arrowCalibrate);

try {
  new arrowCalibrate(1);
} catch (error) {
  console.log(`new with arrow: ${error.constructor.name}`);
}

function outerFunction() {
  const innerArrow = () => arguments[0];
  return innerArrow();
}
console.log(outerFunction("outer-arg"));

const withRest = (...args) => args.length;
console.log(withRest(1, 2, 3));

console.log(arrowCalibrate.name);
console.log(arrowCalibrate.length);
```

```
false
true
function
new with arrow: TypeError
outer-arg
3
arrowCalibrate
1
```

The second lesson wrote the first step of the `new` operator as "create an empty
object whose prototype is bound to the function's `prototype` object." Because no such
object exists for an arrow function, that step can never run at all; the error is a
consequence of this absence.

`arguments` is also resolved lexically: used inside an arrow function, it gives the
arguments of the function that wraps it. Rather than relying on this behavior, rest
parameters should be used; they work the same way in both arrow functions and regular
functions and produce a real array. The last two lines show that arrow functions still
carry ordinary function properties like name and parameter count.

## Arrow Function as a Class Field

Assigning an arrow function to a field in a class body solves the binding problem
definitively. Its cost is paid in the prototype model.

```js
class MeasurementCollector {
  constructor(sensor) {
    this.sensor = sensor;
    this.values = [];
  }

  add(value) {
    this.values.push(value);
    return this.values.length;
  }

  addArrow = (value) => {
    this.values.push(value);
    return this.values.length;
  };
}

const a = new MeasurementCollector("S-01");
const b = new MeasurementCollector("S-02");

console.log(Object.getOwnPropertyNames(MeasurementCollector.prototype).join(","));
console.log(Object.getOwnPropertyNames(a).join(","));
console.log(a.add === b.add);
console.log(a.addArrow === b.addArrow);

const detachedArrow = a.addArrow;
console.log(detachedArrow(21.4));
console.log(a.values.join(","));

console.log(a.addArrow.call(b, 99));
console.log(a.values.join(","));
console.log(b.values.join(",") || "(empty)");
```

```
constructor,add
addArrow,sensor,values
true
false
1
21.4
2
21.4,99
(empty)
```

The first two lines show the distinction: `add` sits on the prototype, `addArrow` is
every instance's own property. The third and fourth lines measure the consequence —
the prototype's method is the same function across every instance, the arrow field is
separate on every instance.

The fifth and sixth lines show the gain: the arrow field, detached from its object,
keeps writing to the correct collector. The last three lines are the other side of that
same trait — trying to redirect it to another collector with `call` has no effect; the
value is still written to object `a`.

The trade-off is clear. The arrow field guarantees context but produces one function
object per instance, removes sharing, and makes redirection impossible. The
prototype's method is shared and redirectable but entrusts context to the call site.
The `bind` pattern in the previous lesson's constructor is another spelling of this
same trade-off.

## Where the Arrow Function Is Wrong

As a rule: **if a function needs to reach its object through `this`, it cannot be an
arrow function.** This covers methods in object literals and behavior written onto a
prototype.

```js
const measurementBehavior = {
  format: () => `${this?.sensor}`,
};

const record = Object.create(measurementBehavior);
record.sensor = "S-01";
console.log(record.format());

const correctBehavior = {
  format() {
    return `${this.sensor}`;
  },
};
const correctRecord = Object.create(correctBehavior);
correctRecord.sensor = "S-01";
console.log(correctRecord.format());

const counterFactory = () => {
  let count = 0;
  return {
    increment: () => (count += 1),
    read: () => count,
  };
};

const counter = counterFactory();
counter.increment();
counter.increment();
console.log(counter.read());
```

```
undefined
S-01
2
```

The first record correctly has its prototype set, but the function in the chain reads
`this` not from the chain but from its own defining scope; the result is `undefined`.
Correct behavior is obtained just by changing the definition form.

The last section previews the second pattern where arrow functions are used correctly
in advance: when an object's data is held in a **closed-over variable** instead of
`this`, `this` is never needed at all and the binding problem disappears. This setup is
called a closure and is the next lesson's subject.

## Summary

- Arrow functions have no `this` binding of their own; the `this` in their body is
  resolved lexically in the scope they are defined in.
- `call`, `apply`, and `bind` cannot change an arrow function's context; they silently
  have no effect.
- An arrow function automatically carries context in callbacks given from inside a
  method; it is the right choice when the caller provides no context.
- Arrow functions have no `prototype`, `arguments`, or `new.target`; this is why they
  cannot be used as constructors.
- An arrow function defined as a class field produces one copy per instance: it
  guarantees context but loses sharing and redirectability.
- Methods in object literals and behavior written onto a prototype cannot be arrow
  functions.

## Next Step

In the last example, a counter kept its state not in an object property but in a local
variable of the function that produced it. That variable kept living even after the
function returned, and was visible only to the functions returned from it. The next
lesson covers this structure — closures; it examines the lifetime of closed-over
variables and compares this way of hiding state with the prototype chain.
