Anonymous vs. Arrow Functions

At first sight, it may appear that arrow functions are direct replacements for anonymous functions. You may be tempted to readily replace anonymous functions with arrow functions. But that may result in code that behaves differently than you intend. There are many semantic differences between anonymous functions and arrow functions, and we have to carefully choose between them based on the context. Learning about the key differences between the two types of functions will help you pick the right one.

Lexical vs. Dynamic Scoping

Most variables used in any function either come from the parameters or are defined locally. However, some variables can be defined in an external context. Let’s consider a short example.

 [1, 2, 3].map(​function​(e) { ​return​ e * 2; })

The map() function takes an anonymous function as an argument. Within the anonymous function, we refer to a variable e in the expression e * 2. The e variable comes from the parameter passed to the anonymous function. No surprises here. Now, let’s consider a small variation to this code.

 [1, 2, 3].map(​function​(e) { ​return​ e * factor; })

The anonymous function passed to the map() function still receives only one parameter—map() will pass only one argument when it calls the function passed as an argument to it. However, within the anonymous function we use two variables, e and factor. The e variable is still the parameter passed to the function. However, the factor variable is neither a parameter nor a locally defined variable. Where does that come from?

There are two possibilities. The variable may come from the scope where the function using the variable is defined—that’s called lexical scoping. Alternatively, the variable may be provided by the caller for the function—that’s called dynamic scoping.

Most programming languages favor lexical scoping. A few languages use dynamic scoping. JavaScript is special—it does both, depending on the variable—and that has been a source of errors in anonymous functions.

JavaScript scopes all non-parameter, non-local variables to lexical scope for anonymous functions, except for this and arguments. Arrow functions have consistent lexical scoping for all non-parameter, non-local variables.

Lexically Scoped this and arguments

Anonymous functions use dynamic scoping for this and arguments and uses lexical scoping for all other variables. This behavior is often a source of error and unpleasant workarounds—recall that rest parameters are preferable to arguments in modern JavaScript, but the this keyword is still a nemesis. Let’s look at an example that illustrates the odd behavior of this.

 this​.stuff = ​'from lexical scope'​;
 const​ someValue = 4;
 const​ self = ​this​;
 
 setTimeout(​function​() {
  console.log(​'someValue is '​ + someValue); ​//lexical scope for someValue
  console.log(​'this...'​ + ​this​.stuff); ​//dynamic scope for this
  console.log(​'self...'​ + self.stuff); ​//lexical scope for self
 }, 1000);

Outside of the anonymous function we have assigned a value from lexical scope to a stuff property of this—the context object. We also created a local variable named someValue and a variable named self, then we assigned the reference this to self. Within the anonymous function passed to setTimeout(), we have not defined someValue, this, or self, but we readily use those three. Let’s look at the output of this code:

 someValue is 4
 this...undefined
 self...from lexical scope

The variable someValue has lexical scope inside the anonymous function, so it gets the value assigned from outside the anonymous function. The variable this, however, is dynamically scoped—it’s whatever setTimeout() passes. Since the this passed by setTimeout() does not have a stuff property, it gets printed as undefined. Finally, self also has lexical scope within the anonymous function, and we’re able to get the stuff property of it—which is the value set into this in the outside scope.

Since this has a different scope compared to other variables, programmers often have to use workarounds like self to access this from the outside scope. It’s easy to miss and use this directly without the intention of using dynamic scoping, and that leads to errors in programming.

Arrow functions keep scope consistent for this.

Let’s change the previous anonymous function into an arrow function to see this behavior.

 setTimeout(() => {
  console.log(​'someValue is '​ + someValue); ​//lexical scope for someValue
  console.log(​'this...'​ + ​this​.stuff); ​//lexical scope for this
  console.log(​'self...'​ + self.stuff); ​//lexical scope for self
 }, 1000);

The only change to this code was replacing

 function() {

with

 () => {

It’s a small change to the syntax but a big change to the semantics. Now, this within the arrow function is lexically scoped much like someValue, as we see from the output:

 someValue is 4
 this...from lexical scope
 self...from lexical scope

The example illustrates a few key differences. It is very safe to use this within arrow functions and count on it being the value in the lexical scope. When using arrow functions, we do not need workarounds like the self or that variable that programmers often assign this to.

If your code relied on this from dynamic scoping, then arrow functions are not the right choice. You’ll want to continue to use anonymous functions in such cases. Use your judgment.

In addition to this, anonymous functions use dynamic scoping for arguments. Here’s an example to illustrate that difference in scoping of arguments between anonymous functions and arrow functions.

1: const​ create = ​function​(message) {
2:  console.log(​'First argument for create: '​ + ​arguments​[0]);
3: return​ ​function​() {
4:  console.log(​'First argument seen by greet: '​ + ​arguments​[0]);
5:  };
6: };
7: 
8: const​ greet = create(​'some value'​);
9: greet(​'hi'​);

The create() function creates and returns to the caller an anonymous function. It also prints the first argument passed to it before returning the anonymous function. Outside of the create() function, we store into the greet variable the function returned by a call to create(). Finally we invoke that function, passing the argument hi. Here’s the output of this code:

 First argument for create: some value
 First argument seen by greet: hi

The output confirms that the anonymous function uses dynamic scoping for arguments—it reports the value hi passed by its caller and not the value some value from the context in which the function is created—that is, the create() method. In other words, the variables that are both named arguments on lines 2 and 4 are not the same.

Now, let’s replace the anonymous function created within the create() method with an arrow function.

 const​ create = ​function​(message) {
  console.log(​'First argument for create: '​ + ​arguments​[0]);
 return​ () => console.log(​'First argument seen by greet: '​ + ​arguments​[0]);
 };
 
 const​ greet = create(​'some value'​);
 greet(​'hi'​);

The code is concise compared to the previous version, but the difference is not just in syntax. Let’s look at the output after this change:

 First argument for create: some value
 First argument seen by greet: some value

The variable arguments within the arrow function is lexically scoped. Thus the variable arguments within the arrow function now refers to the same variable that is visible within the create() function. The value hi passed to the call is not bound to the arguments in this case.

At first sight this might appear like a hindrance at the least and possibly an error. However, this is the intended behavior of arrow functions—they want to use lexical scoping consistently for this, arguments, and any other non-parameter, non-local variable.

If your function relies on using arguments, then stick with the anonymous function instead of converting it to an arrow function. If you do decide to convert to an arrow function, then change arguments to a rest parameter.

From here on, when writing JavaScript, don’t use arguments—as we saw in Using the Rest Parameter, rest parameters are far better than arguments.

Differences in bind, call, apply

The bind() function is useful to attach or curry arguments to functions so that the attached values are used in calls to the bound or curried function. Here’s an example.

 const​ greet = ​function​(message, name) {
  console.log(message + ​' '​ + name);
 };
 
 const​ sayHi = greet.bind(​null​, ​'hi'​);
 
 sayHi(​'Joe'​);

sayHi is a reference to a function that has curried or saved away a value for the message parameter of greet(). Later, when sayHi is called, it passes the curried value hi to message to the greet() function. The result of the previous code is

 hi Joe

While bind() is a powerful function to curry arguments, there is one sore point about it: the first parameter. If we want to curry n parameters, we pass n + 1 arguments to bind(), and the first argument binds to this. Since arrow functions use the lexical this, we can’t bind this if bind() is called on arrow functions. So the first parameter of bind() makes little sense when used in the context of arrow functions, and so the irrelevant first argument passed is largely ignored.

While the bind() function may be used to bind this and its parameters, when used on an arrow function, for example, anArrowFunc.bind(null, someinput), bind() can only bind parameters and not this—the first argument, irrespective of whether it is null or something else, is ignored.

If a function relies on this, the call and the apply functions help to pass values for this in addition to the parameters of the function. Since arrow functions already bind this to their lexical scope, it makes little sense to use call or apply with arrow functions. Furthermore, since we have the spread operator, apply has fallen out of favor. Don’t use call or apply with arrow functions.

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

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