A.6. The fat arrow

The fat arrow is a newer JS feature that solves a long-standing problem in the language. JS’s bind method, in addition to apply, solved this prior to ES2015. The fat arrow, or the arrow function, now solves the scope problem in a more readable way. This problem isn’t unique to Web Components, or classes, for that matter. The fat arrow allows us to preserve scope anywhere we need to. For Web Component classes specifically, it makes our event listeners and callbacks much more readable and easier to use.

A.6.1. The callback scope problem

You’re probably familiar with event listeners, as we use them all the time when checking for mouse events, keyboard input, as well as lots of other things. Typically, you’ll use them with two parameters, the first being a string describing the event to listen for and the second being the function to call when the thing you’re listening for dispatches an event.

Ignoring the fat arrow syntax for now, the syntax is typically

target.addEventListener('mousemove', function(event) {
        ...do something
});

Or, if you have a function already set up to handle the event:

target.addEventListener('mousemove', myFunction);

In each of these cases, a rather unfortunate thing happens: we’ve lost our original scope, and in the function that gets called, we have a brand-new scope. To explain, let’s take a peek at the next listing for an example.

Listing A.22. A timer example showing loss of scope within a class
class ScopeTest {
   constructor() {
       this.message = 'hi';
       setInterval(this.onTimer, 1000);       1
   }
   onTimer() {
       console.log(this.message);             2
   }
}
let test = new ScopeTest();

  • 1 Starts a timer with the onTimer function being called on every timeout
  • 2 Undefined, because “this” is no longer in the class scope

With this example, we instantiate a class. Right off the bat, in the constructor, we set a string called message to “hi”. We also start a timer that fires every second. The timer calls a function called onTimer, which console-logs our message variable.

A.6.2. Losing scope in classes

The problem is that when you run this code, undefined is logged to the console. Why doesn’t “hi” get logged? What would happen if we changed our constructor to directly call the function?

Listing A.23. Directly calling a function to avoid scope loss
class ScopeTest {
   constructor() {
       this.message = 'hi';
       this.onTimer();                  1
   }

   onTimer() {
       console.log(this.message);       2
   }
}
let test = new ScopeTest();

  • 1 Calls the onTimer function directly instead of through setInterval
  • 2 Will log “hi” because “this” is still in the class’s scope

In this case, our message of “hi” is indeed logged, but the difference between these two methods is a matter of scope.

Scope is the context in which we can access variables, functions, and objects. In fact, the reference this is that context. Try adding the following console log to your constructor:

  constructor() {
       console.log(this);
       this.message = 'hi';
       this.onTimer();
   }

What gets logged is this:

ScopeTest {message: "hi"}

And, of course, if you expand in your dev tools, you’ll be able to see the onTimer function in there as well. Logging this in your class’s scope gives you a reference to your class! Exactly what we need for managing code in our class, and it’s why we can access variables through this or call functions on this.

On the other hand, if we set up a proper timer in the constructor using setTimeout(this.onTimer, 1000) and log this from our timer callback, like so,

   onTimer() {
       console.log(this);
   }

we find ourselves inside an unrecognizable scope:

Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, . . .}

In fact, if you were to run onTimer from the constructor without the timer, your scope would still be that of your class.

So, as you can probably see, when you pass a function to something like an event listener, a timer, or similar to be called back at a later time, your callback function is suddenly in a new scope! Of course, this is problematic, because the question then becomes how to reference your class’s scope again from your callback.

With our example, within our timer callback, we can’t access our message variable. What if, instead, we wanted to take action on our Web Component’s DOM? A mouse click wouldn’t be terribly useful to listen for if we couldn’t take some kind of contextual action on it.

A.6.3. Managing scope with the fat arrow

There have been many ways to tackle this problem over time in JS, but we finally have a new JS feature specifically for it, and that is the fat arrow. Of course, we affectionately refer to this as the fat arrow because the syntax of => is an arrow with a thick stem.

To use the fat arrow in the next listing, we pass an arrow function containing an expression that calls our method instead of the function itself.

Listing A.24. Using the fat arrow to maintain scope
class ScopeTest {
   constructor() {
       this.message = 'hi';
       setInterval(() => this.onTimer(), 1000);      1

   }
   onTimer() {
       console.log(this.message);                    2

   }
}
let test = new ScopeTest();

  • 1 Passes a fat arrow expression instead of a function
  • 2 Class scope is preserved; the message “hi” is logged

Using the fat arrow here, we can log this.message, and it’s not undefined; more importantly, we are preserving the scope of our class even through a callback. Oftentimes, you won’t see the empty parentheses in a fat arrow expression. Here, it denotes that we’re not passing parameters, since setInterval callbacks don’t take them.

If there were parameters for a given method, those parentheses would be filled in as you might expect:

callback((x, y, z) => this.onCallback(x, y, z));

For an example that directly applies to us, let’s circle back to our mouse-move listener:

this.addEventListener('mousemove', e => this.onMouseMove(e));

In this case, an event listener passes an event into the callback, and we’re choosing to use it. We could still ignore this event and use the function with the empty parentheses, like this:

this.addEventListener('mousemove', () => this.onMouseMove());

Either way, we can use the fat arrow to properly preserve scope, as figure A.8 shows, where we contrast this against the other methods we discussed. More importantly for our Web Component use cases, we can keep scope directly to the Web Component itself, and writing code for our component is kept easy without becoming a maze of mixed scopes we need to manage.

Figure A.8. Showing loss of scope or context when using a callback with no way to get back, and how the fat arrow can map scope back to where the function was called, just like calling a normal function

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.165.131