Modern JavaScript: Arrow Function Scoping
Welcome to the Arrow Function Scoping lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
An earlier lesson showed arrow functions, which are often used for callback functions.
>
[1, 2, 3].map(x => x * 2);Result:
Arrow functions are terse, which is nice. But they have a second big benefit: their scoping rules are easier to understand than regular functions' scoping rules.
Regular functions and shorthand methods have confusing
thisbehavior that depends on how they're called. For example, inside of anaddressobject, we can accessthis.cityandthis.country.>
const address = {city: 'Paris',country: 'France',addressFunction() {return `${this.city}, ${this.country}`;},};address.addressFunction();Result:
However, if
addressFunctionreturns another function, now the inner function'sthisis undefined.>
const address = {city: 'Paris',country: 'France',addressFunction() {return function() {return `${this.city}, ${this.country}`;};},};address.addressFunction()();Result:
It's possible to fix this problem with
.bind(...), but that's very awkward.>
const address = {city: 'Paris',country: 'France',addressFunction() {const f = function() {return `${this.city}, ${this.country}`;};return f.bind(this);},};address.addressFunction()();Result:
Fortunately, there's an easier solution: we can use an arrow function. Arrow functions always inherit the scope that they were defined in, including the
thisvalue.Here's the example above rewritten with an arrow function. It's one line of code instead of three. No need for any confusing
bindcalls!>
const address = {city: 'Paris',country: 'France',addressFunction() {/* Inside of this shorthand method, `this` is the containing object,* `address`. Arrow functions inherit the scope that they were defined in,* so this arrow function's `this` is also the containing object,* `address`. */return () => `${this.city}, ${this.country}`;},};address.addressFunction()();Result:
'Paris, France'
Here's a code problem:
Finish the
volumeFunctionshorthand method on the 3D rectangle below. It should return a function. The returned function should calculate and return the 3D rectangle's volume. (Volume is calculated asthis.baseArea() * this.height.)The function that you return should be an arrow function, which will ensure that it can see the rectangle's
this.const rectangle3D = {width: 3,depth: 4,height: 5,baseArea() { return this.width * this.depth; },volumeFunction() { return () => this.baseArea() * this.height; },};rectangle3D.volumeFunction()();- Goal:
60
- Yours:
60
In the problem above, we made
volumeFunctiona shorthand method. Itsthisreferred to the parent object,rectangle3D. When we referencethis.height, we getrectangle3D.height.What if we make
volumeFunctionitself an arrow function? Unfortunately, that doesn't work!>
const rectangle3D = {width: 3,depth: 4,height: 5,baseArea() { return this.width * this.depth; },// This is an arrow function.volumeFunction: () => { return () => this.baseArea() * this.height; },};rectangle3D.volumeFunction()();Result:
The problem is that the
volumeFunctionis now an arrow function, which doesn't "know" that it's inside of an object. Arrow functions inherit the scope that they're defined in, which in this case is the global scope.volumeFunction'sthisrefers to the globalthis. In a browser, that'swindow; and in Node, it's theglobalobject. Neither of those objects has a.baseArea()or.height, hence the error above!Here's a way to see that more clearly. The next example is the same as the one above, with one difference: we define the arrow function outside of the object, then we assign it to
rectangle3D.volumeFunction. We get the same result, but now it's easier to see thatvolumeFunctiondoesn't know that it's inside ofrectangle3D.>
const rectangle3D = {width: 3,depth: 4,height: 5,baseArea() { return this.width * this.depth; },};const volumeFunction = () => { return () => this.baseArea() * this.height; };rectangle3D.volumeFunction = volumeFunction;rectangle3D.volumeFunction()();Result:
If all of these scoping rules seem confusing, that's because they are! Fortunately, the rule for arrow functions is simple: arrow functions always inherit the scope that they were defined in. This makes them ideal for use as callback functions, where we often want to reference variables in the function or class that created the callback function.
Here's a helpful rule that works in most situations.
If the function defined is part of an object, and needs to access that object's properties, use a shorthand function.
If it's a standalone function or a method that doesn't need to access
this, we can define it however we want: as a shorthand method, an arrow function, or using thefunctionkeyword. The scoping rules forthisdon't affect it because it doesn't accessthisin the first place!This rule is useful because it makes sense even if we forget about JavaScript's surprising
thisbehavior. In every programming language, the difference between functions and methods is that methods are part of an object, whereas functions are defined independently. If it accesses the object's internals, it should be a method. If it doesn't access the internals, it can be a function or a method.