Arrow Functions in JavaScript: How to Use Clean Syntax

Learn all about JavaScript arrow functions. We'll show you how to use ES6 arrow syntax, and some common mistakes to watch out for when utilizing arrow functions in your code. You will see many examples to illustrate how they work.

JavaScript arrow functions emerged with the release of ECMAScript 2015 (also known as ES6). Arrow functions quickly became a favorite feature of developers due to their clean syntax and handling of the *this* keyword.

Arrow Function Syntax: Overriding Regular Functions

Functions are like recipes, where you store useful instructions to do what needs to be done in your program, such as performing an action or returning a value. By calling your function, you execute the steps contained in the recipe. This can be done every time the function is called without having to rewrite the recipe again and again.

Here's the standard way to declare a function and then call it from JavaScript:

// function declaration
function sayHiStranger() {
  return 'Hi, stranger!'
}

// call the function
sayHiStranger()

You can also write functions that do the same as function expressions, like so:

const sayHiStranger = function () {
  return 'Hi, stranger!'
}

JavaScript arrow functions are always expressions. Here's how to rewrite the above function using bold arrow notation:

const sayHiStranger = () => 'Hi, stranger'

The benefits of doing this include:

  • just one line of code

  • no functionkeywords

  • no returnkeywords

  • and without curly braces {}

In JavaScript, functions are "first class citizens". You can store functions in variables, pass them as arguments to other functions, and return them as values ​​from other functions. You can do all of this with JavaScript arrow functions.

No parenthesis syntax

In the example above, the function has no parameters. ()In this case, you must add a set of empty parentheses before the bold arrow ( ) symbol =>. The same applies when you have multiple parameters:

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`
// call the function
console.log(getNetflixSeries('Bridgerton', '2020') )
// output: The Bridgerton series was released in 2020

However, you can continue to omit the parentheses as long as there is one parameter (you don't have to, but you can):

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"
// call the function
console.log(favoriteSeries("Bridgerton"))
// output: "Let's watch it"

Be careful though. For example, if you decide to use default arguments, you must enclose them in parentheses:

// with parentheses: correct
const bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`
// outputs: "Bridgerton is the best"
console.log(bestNetflixSeries())

// no parentheses: error
const bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`
// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

implicit return

ES6 arrow syntax can be made more concise when there is only one expression in the function body. You can keep everything on one line, remove curly braces, and get rid of keywords return.

You just saw how these one-liners work in the example above. Here's another example, just for good measure. The orderByLikes()function does what it says on the surface: namely, it returns an array of Netflix series objects sorted by highest number of likes:

// using the JS sort() function to sort the titles in descending order 
// according to the number of likes (more likes at the top, fewer at the bottom
const orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )

// call the function 
// output:the titles and the n. of likes in descending order
console.log(orderByLikes)

That's cool, but watch out for code readability - especially when sorting a bunch of arrow functions using one-liners and parenthesis-free ES6 arrow syntax, as in the following example:

const greeter = greeting => name => `${greeting}, ${name}!`

What happened there? Try using regular function syntax:

function greeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!` 
  }
}

Now you can quickly see how outer functions greeterhave parameters, greetingand return anonymous functions. This inner function in turn has a parameter named and namereturns a string with the values ​​of and. Here's how to call the function:greeting``name

const myGreet = greeter('Good morning')
console.log( myGreet('Mary') )   

// output: 
"Good morning, Mary!"

Note that these implicitly return errors

When your JavaScript arrow function contains multiple statements, you need to enclose them all in curly braces and use keywords return.

In the code below, the function builds an object that contains the titles and summaries of some Netflix series (Netflix reviews are from Rotten Tomatoes):

const seriesList = netflixSeries.map( series => {
  const container = {}
  container.title = series.name 
  container.summary = series.summary

  // explicit return
  return container
} )

An arrow function inside a function .map()consists of a series of statements, returning an object at the end of the statement. This makes the use of curly braces around function bodies unavoidable.

Also, implicit return is not an option when you use curly braces. You must use returnkeywords.

If your function returns an object literal using implicit return, you need to enclose the object in parentheses. Failure to do so will result in an error because the JavaScript engine incorrectly parses object literal braces as function braces. As you just noticed above, when you use curly braces in an arrow function, you cannot omit the return keyword.

A shorter version of the previous code demonstrates this syntax:

// Uncaught SyntaxError: unexpected token: ':'
const seriesList = netflixSeries.map(series => { title: series.name });

// Works fine
const seriesList = netflixSeries.map(series => ({ title: series.name }));

you can't name an arrow function

A function without a name identifier between the keyword and the parameter list functionis called an anonymous function. A regular anonymous function expression looks like this:

const anonymous = function() {
  return 'You can't identify me!' 
}

Arrow functions are anonymous functions:

const anonymousArrowFunc = () => 'You can't identify me!'

Starting with ES6, variables and methods can use attributes of anonymous functions to infer their names from their syntactic position name. This makes it possible to identify the function when checking its value or reporting an error.

Check it out with the following command anonymousArrowFunc:

console.log(anonymousArrowFunc.name)
// output: "anonymousArrowFunc"

Note that this inferred property only exists when an anonymous function is assigned to a variable name, as in the example above. If you use anonymous functions as callbacks, you lose this useful feature. The following demo exemplifies this, where anonymous functions inside methods .setInterval()cannot take advantage of this nameproperty:

let counter = 5
let countDown = setInterval(() => {
  console.log(counter)
  counter--
  if (counter === 0) {
    console.log("I have no name!!")
    clearInterval(countDown)
  }
}, 1000)

That's not all. This inferred nameproperty still doesn't work as a proper identifier that you can use to refer to the function from within - e.g. recursion, unbinding events, etc.

How arrow functions handle thiskeywords

The most important thing to remember about arrow functions is the way they handle keywords this. In particular, thiskeywords inside arrow functions do not bounce.

To illustrate what this means, check out the demo below:


[codepen_embed height=”300″ default_tab=”html,result” slug_hash=”qBqgBmR” user=”SitePoint”]See Pen JS this in arrow functions via SitePoint ( @SitePoint ) on CodePen. [/codepen_embed]

It's a button. Clicking the button triggers a reverse counter from 5 to 1, which is displayed on the button itself.

<button class="start-btn">Start Counter</button>

...

const startBtn = document.querySelector(".start-btn");

startBtn.addEventListener('click', function() {
  this.classList.add('counting')
  let counter = 5;
  const timer = setInterval(() => {
    this.textContent = counter 
    counter -- 
    if(counter < 0) {
      this.textContent = 'THE END!'
      this.classList.remove('counting')
      clearInterval(timer)
    }
  }, 1000) 
})

Note that event handlers inside methods .addEventListener()are regular anonymous function expressions, not arrow functions. Why? If you log into thisthe function, you'll see that it references the button element the listener is attached to, which is exactly what is expected and what is needed for the program to work as planned:

startBtn.addEventListener('click', function() {
  console.log(this)
  ...
})

Here's what the Firefox Developer Tools console looks like:

4d3cfe05f94eaa2a7f82f3ef4c331d8f.png

However, try replacing a regular function with an arrow function, like so:

startBtn.addEventListener('click', () => {
  console.log(this)
  ...
})

Now, thisthe button is no longer referenced. Instead, it refers to the Windowobject:

6994619cc71773fb56eecadaab242720.png

This means that, for example, if you want to thisadd a class to a button after it is clicked, your code won't work:

// change button's border's appearance
this.classList.add('counting')

When you use arrow functions in JavaScript, the value of the keyword thisdoes not bounce. It is inherited from the parent scope (this is called **lexical scope**). In this particular case, the arrow function in question is passed as an argument to the startBtn.addEventListener()method, which is in the global scope. Therefore, thisinside the function handler is also bound to the global scope, i.e. to Windowthe object.

So, if you want to thisrefer to the start button in the program, the correct way is to use a regular function instead of an arrow function.

anonymous arrow function

The next thing to notice in the demo above is the code inside the method .setInterval(). Here, too, you'll find an anonymous function, but this time it's an arrow function. Why?

thisNote what the value of would be if you used a regular function:

const timer = setInterval(function() {
  console.log(this)
  ...
}, 1000)

Will it be buttonan element? not at all. This will be an Windowobject!

64cbb4b8d81c6c3ba02181a8012d1e00.png

In fact, the context has changed, because thisit is now inside an unbound or global function, which is passed as an argument to .setInterval(). Therefore, the value of the keyword thishas also changed, because it is now bound to the global scope.

A common trick in this case is to include another variable to store the value of the keyword thisso that it keeps referencing the expected element - in this case the element button:

const that = this
const timer = setInterval(function() {
  console.log(that)
  ...
}, 1000)

You can also use the following methods .bind()to solve the problem:

const timer = setInterval(function() {
  console.log(this)
  ...
}.bind(this), 1000)

With arrow functions, the problem disappears entirely. thisHere are the values ​​when using arrow functions:

const timer = setInterval( () => { 
  console.log(this)
  ...
}, 1000)

b7702e60a6ca22abefe431b2ccbe9e3f.png

This time, the console logs the button, which is what we want. In fact, the program is going to change the button text, so thisthis buttonelement needs to be referenced:

const timer = setInterval( () => { 
  console.log(this)
 // the button's text displays the timer value
  this.textContent = counter
}, 1000)

Arrow functions do not have their own thiscontext. They inherit the values ​​of their parents this, and it is because of this trait that they make good choices in the above situations.

JavaScript arrow functions aren't always the right tool for the job

Arrow functions aren't just a fancy new way to write functions in JavaScript. They have their own limitations, which means there are some situations where you don't want to use them. The click handler in the previous demo is a good example, but it's not the only one. Let's check some more.

Arrow functions as object methods

Arrow functions don't play well as methods on objects. Here is an example.

Consider this netflixSeriesobject, it has some properties and a few methods. The call console.log(netflixSeries.getLikes())should print a message containing the current like count, and the call console.log(netflixSeries.addLike())should increment the like count by one, then print the new value on the console along with a thank you message:

const netflixSeries = {
  title: 'After Life', 
  firstRealease: 2019,
  likes: 5,
  getLikes: () => `${this.title} has ${this.likes} likes`,
  addLike: () => {  
    this.likes++
    return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
  } 
}

Instead, calling this .getLikes()method returns "undefined has NaN likes", and calling this .addLike()method returns "Thank you for Like undefine, it now has NaN likes". Therefore, and cannot refer to properties of an object and this.titlerespectively .this.likestitlelikes

The problem is again with the lexical scope of the arrow function. The object's internal thismethods refer to the parent's scope, which in this case is the object Window, not the parent itself - that is, not the object netflixSeries.

The solution, of course, is to use a regular function:

const netflixSeries = {
  title: 'After Life', 
  firstRealease: 2019,
  likes: 5,
  getLikes() {
    return `${this.title} has ${this.likes} likes`
  },
  addLike() { 
    this.likes++
    return `Thank you for liking ${this.title}, which now has ${this.likes} likes`
  } 
}

// call the methods 
console.log(netflixSeries.getLikes())
console.log(netflixSeries.addLike())

// output: 
After Life has 5 likes
Thank you for liking After Life, which now has 6 likes

Arrow functions with third-party libraries

Another thing to watch out for is that third-party libraries often bind method calls so that the thisvalue points to something useful.

For example, in a jQuery event handler, thisyou can access the DOM element the handler is bound to:

$('body').on('click', function() {
  console.log(this)
})
// <body>

But if we use an arrow function - which, as we have seen, has no context of its own this- we get unexpected results:

$('body').on('click', () =>{
  console.log(this)
})
// Window

Here's another example using Vue:

new Vue({
  el: app,
  data: {
    message: 'Hello, World!'
  },
  created: function() {
    console.log(this.message);
  }
})
// Hello, World!

Inside the hook created, thisbind to the Vue instance, so the "Hello, World!" message is displayed.

However, if we use an arrow function, thisit will point to the parent scope without properties message:

new Vue({
  el: app,
  data: {
    message: 'Hello, World!'
  },
  created: function() {
    console.log(this.message);
  }
})
// undefined

Arrow function has no argumentsobject

Sometimes, you may need to create a function with an infinite number of parameters. For example, suppose you want to create a function that lists your favorite Netflix series in order of preference. However, you don't yet know how many series to include. JavaScript makes the *parameters* object available. This is an array-like object (not a full array) that stores the values ​​passed to the function when called.

Try using an arrow function to achieve this:

const listYourFavNetflixSeries = () => {
  // we need to turn the arguments into a real array 
  // so we can use .map()
  const favSeries = Array.from(arguments) 
  return favSeries.map( (series, i) => {
    return `${series} is my #${i +1} favorite Netflix series`  
  } )
  console.log(arguments)
}

console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))

When you call this function, you will receive the following error message: Uncaught ReferenceError: arguments is not defined. This means that the argumentsobject is not available inside the arrow function. In fact, replacing the arrow function with a regular function does the trick:

const listYourFavNetflixSeries = function() {
   const favSeries = Array.from(arguments) 
   return favSeries.map( (series, i) => {
     return `${series} is my #${i +1} favorite Netflix series`  
   } )
   console.log(arguments)
 }
console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))

// output: 
["Bridgerton is my #1 favorite Netflix series",  "Ozark is my #2 favorite Netflix series",  "After Life is my #3 favorite Netflix series"]

So if you need the argumentsobject, you can't use arrow functions.

But what if you really want to use arrow functions to replicate the same functionality? One thing you can do is use ES6 rest parameters (  ...). Here's how to override the function:

const listYourFavNetflixSeries = (...seriesList) => {
   return seriesList.map( (series, i) => {
     return `${series} is my #${i +1} favorite Netflix series`
   } )
 }

in conclusion

By using arrow functions, you can write neat one-liners with implicit returns, and finally forget about old-fashioned tricks to work around thiskeyword binding in JavaScript. Arrow functions also work well with array methods like .map(), .sort(), .forEach(), .filter()and .reduce(). But remember: arrow functions don't replace regular JavaScript functions. Remember to use JavaScript arrow functions only if they are the right tool for the job.

Guess you like

Origin blog.csdn.net/weixin_42981560/article/details/132332049