Стрелочные функции ES6: жирный и сжатый синтаксис в JavaScript

Лучшие проекты JavaScript для начинающих Программирование и разработка

Лучшие проекты JavaScript для начинающих

В этой статье вы узнаете все о синтаксисе стрелочных функций JavaScript, включая некоторые подводные камни, о которых вам нужно знать при использовании стрелочных функций в вашем коде. Вы увидите множество примеров, иллюстрирующих, как они работают.

Стрелочные функции были введены в JavaScript с выпуском ECMAScript 2015, также известного как ES6. Их краткий синтаксис и то, как они обрабатывают ключевое слово this, являются одними из основных функций, которые способствовали значительному успеху стрелочных функций среди разработчиков.

Превращение функции до ES6 в функцию стрелки

Вы можете рассматривать функции как своего рода рецепт, в котором вы храните полезные инструкции для выполнения того, что вам нужно сделать в вашей программе, например, выполнения действия или возврата значения. Вызывая свою функцию, вы выполняете шаги, включенные в ваш рецепт, и вы можете делать это каждый раз, когда вы вызываете эту функцию, без необходимости переписывать рецепт снова и снова.

Вот стандартный способ объявить функцию и затем вызвать ее в JavaScript:

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

// call the function
sayHiStranger()

Вы также можете написать ту же функцию как выражение функции, например:

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

Стрелочные функции всегда являются выражениями. Вот как вы могли бы переписать указанную выше функцию, используя обозначение жирной стрелки:

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

Преимущества этого включают:

  • всего одна строчка кода
  • нет functionключевого слова
  • нет returnключевого слова
  • без фигурных скобок {}

В JavaScript функции — это «граждане первого класса». То есть вы можете хранить функции в переменных, передавать их другим функциям в качестве аргументов и возвращать их из других функций как значения. Все это можно сделать с помощью стрелочных функций.

Давайте рассмотрим различные способы написания стрелочных функций.

Синтаксис без родителей

В приведенном выше примере функция не имеет параметров. В этом случае вы должны добавить набор пустых скобок ()перед жирной стрелкой ( =>). То же самое происходит, когда у вас более одного параметра :

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

Однако с помощью всего одного параметра вы можете пойти дальше и опустить круглые скобки (вам не обязательно, но можно):

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"

Но будьте осторожны. Если, например, вы решили использовать параметр по умолчанию, вы должны заключить его в круглые скобки :

// 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)

И то, что вы можете, не означает, что вы должны это делать. Смешанный с легким беззаботным, благонамеренным сарказмом, Кайл Симпсон (известный в You Don’t Know JS) высказал свои мысли об исключении скобок в этой блок-схеме.

Неявный возврат

Когда у вас есть только одно выражение в теле функции, вы можете оставить все в одной строке, удалить фигурные скобки и отказаться от returnключевого слова. В приведенных выше примерах вы только что видели, как работают эти изящные однострочники. Вот еще один пример, на всякий случай. orderByLikes()Функция делает то, что он говорит на олове: то есть, он возвращает массив из серии Netflix объектов по заказу наибольшего количества подобных:

// 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)

Это круто, но следите за читабельностью вашего кода — особенно при упорядочивании кучи стрелочных функций с использованием однострочных строк и синтаксиса без скобок, как в этом примере :

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

Что там происходит? Попробуйте использовать обычный синтаксис функции:

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

Теперь вы можете быстро увидеть, как внешняя функция greeterимеет параметр greetingи возвращает анонимную функцию. Эта внутренняя функция, в свою очередь, имеет вызываемый параметр nameи возвращает строку, используя значения обоих greetingи name. Вот как можно вызвать функцию:

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

// output: 
"Good morning, Mary!" 

Остерегайтесь этих проблем с неявным возвратом

Если ваша стрелочная функция содержит более одного оператора, вам нужно заключить их все в фигурные скобки и использовать returnключевое слово. В приведенном ниже коде функция создает объект, содержащий заголовок и краткое содержание нескольких сериалов Netflix (обзоры Netflix взяты с веб-сайта Rotten Tomatoes ):

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

  // explicit return
  return container
} )

Стрелочная функция внутри.map()функции развивается из серии операторов, в конце которых она возвращает объект. Это делает неизбежным использование фигурных скобок вокруг тела функции. Кроме того, поскольку вы используете фигурные скобки, неявный возврат невозможен. Вы должны использовать returnключевое слово.

Если ваша стрелочная функция возвращает литерал объекта с использованием неявного возврата, вам необходимо заключить объект в круглые скобки. Невыполнение этого приведет к ошибке, потому что движок JS ошибочно анализирует фигурные скобки литерала объекта как фигурные скобки функции. И как вы только что заметили выше, когда вы используете фигурные скобки в функции стрелки, вы не можете пропустить ключевое слово return.

Этот синтаксис демонстрируется в более короткой версии предыдущего кода:

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

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

Вы не можете назвать стрелочные функции

Функции, у которых нет идентификатора имени между functionключевым словом и списком параметров, называются анонимными функциями. Вот как выглядит обычное выражение анонимной функции:

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

Все стрелочные функции являются анонимными:

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

Начиная с ES6, переменные и методы могут выводить имя анонимной функции из ее синтаксической позиции, используя ее nameсвойство. Это позволяет идентифицировать функцию при проверке ее значения или сообщении об ошибке.

Проверьте это, используя anonymousArrowFunc:

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

Но имейте в nameвиду, что это предполагаемое свойство существует только тогда, когда анонимная функция назначается переменной, как в приведенных выше примерах. Если вы, например, используете анонимную функцию в качестве обратного вызова, вы потеряете эту полезную функцию. Это проиллюстрировано в демонстрации ниже, где анонимная функция внутри.setInterval()метода не может воспользоваться этим nameсвойством:

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

И это еще не все. Это предполагаемое nameсвойство по-прежнему не работает как правильный идентификатор, который можно использовать для обращения к функции изнутри самой себя — например, для рекурсии, событий отмены привязки и т. Д.

Внутренняя анонимность стрелочных функций побудила Кайла Симпсона выразить свое мнение о стрелочных функциях следующим образом:

Поскольку я не думаю, что анонимные функции — хорошая идея для частого использования в ваших программах, я не поклонник использования =>формы стрелочной функции.

Как стрелочные функции обрабатывают ключевое thisслово

Самое важное, что нужно помнить о стрелочных функциях, — это то, как они обрабатывают thisключевое слово. В частности, thisключевое слово внутри стрелочной функции не отскакивает.

Вот кнопка. Нажатие на кнопку запускает обратный счетчик от 5 до 1. Числа отображаются на самой кнопке:

<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) 
})

Обратите внимание, что обработчик событий внутри.addEventListener()метода является регулярным выражением анонимной функции, а не функцией стрелки. Почему? Если вы войдете thisв функцию, вы увидите, что она ссылается на элемент кнопки, к которому был прикреплен слушатель, что является именно тем, что ожидается и что необходимо для того, чтобы программа работала по плану:

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

Вот как это выглядит в консоли инструментов разработчика Firefox:

Однако попробуйте заменить обычную функцию стрелочной функцией, например:

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

Теперь thisбольше не ссылается на кнопку. Вместо этого он ссылается на Windowобъект:

Это означает, что, если вы хотите использовать, например, thisдля добавления класса к кнопке после ее нажатия, ваш код не будет работать:

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

Вот сообщение об ошибке в консоли:

Это происходит потому, что, когда вы используете стрелочную функцию, значение thisключевого слова не восстанавливается, а наследуется от родительской области (это называется лексической областью видимости ). В этом конкретном случае рассматриваемая стрелочная функция передается в качестве аргумента startBtn.addEventListener()методу, который находится в глобальной области видимости. Следовательно, thisвнутренний обработчик стрелочной функции также привязан к глобальной области видимости, то есть к Windowобъекту.

Итак, если вы хотите thisсослаться на кнопку запуска в программе, правильный подход — использовать обычную функцию, а не функцию стрелки.

Следующее, на что следует обратить внимание в приведенной выше демонстрации, — это код внутри.setInterval()метода. Здесь вы также найдете анонимную функцию, но на этот раз это стрелочная функция. Почему?

Обратите внимание, каково было бы значение this, если бы вы использовали обычную функцию:

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

Будет ли это buttonстихией? Нисколько. Это был бы Windowобъект!

Фактически, контекст изменился, поскольку thisтеперь он находится внутри несвязанной или глобальной функции, которая передается в качестве аргумента.setInterval(). Следовательно, значение thisключевого слова также изменилось, поскольку теперь оно привязано к глобальной области. Распространенным взломом в этой ситуации было включение другой переменной для хранения значения thisключевого слова, чтобы оно продолжало ссылаться на ожидаемый элемент — в данном случае buttonэлемент:

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

Вы также можете использовать.bind()для решения проблемы:

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

Со стрелочными функциями проблема вообще исчезает. Вот какое значение thisимеет использование стрелочной функции:

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

На этот раз консоль регистрирует кнопку, а это именно то, что нужно. Фактически, программа собирается изменить текст кнопки, поэтому она должна thisссылаться на buttonэлемент:

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

Стрелочные функции не имеют собственного thisконтекста. Они наследуют значение thisот родителя, и именно благодаря этой функции они делают отличный выбор в ситуациях, подобных описанной выше.

Стрелочные функции — не всегда подходящий инструмент для работы

Стрелочные функции — это не просто новый причудливый способ написания функций в JavaScript. У них есть свои причуды и ограничения, а это значит, что есть случаи, когда вы не хотите использовать функцию стрелки. Обработчик кликов из предыдущей демонстрации является показательным, но не единственным. Давайте рассмотрим еще несколько.

Стрелочные функции как методы объекта

Стрелочные функции плохо работают как методы для объектов. Вот пример. Рассмотрим этот netflixSeriesобъект, у которого есть некоторые свойства и несколько методов. Звонок console.log(netflixSeries.getLikes())должен печатать сообщение с текущим количеством лайков, а звонок console.log(netflixSeries.addLike())должен увеличивать количество лайков на единицу, а затем печатать новое значение с сообщением благодарности на консоли:

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`
  } 
}

Вместо этого вызов.getLikes()метода возвращает «undefined имеет NaN лайков», а вызов.addLike()метода возвращает «Спасибо, что понравился undefined, который теперь имеет NaN лайков». Итак, this.titleи this.likesне могут ссылаться на свойства объекта titleи likesсоответственно.

И снова проблема заключается в лексической области видимости стрелочных функций. thisВнутри метода объекта ссылается объем родителя, который в данном случае является Windowобъектом, а не сам родитель — то есть, не netflixSeriesобъект.

Решение, конечно же, — использовать обычную функцию:

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

Стрелочные функции со сторонними библиотеками

Еще одна проблема, о которой следует помнить, заключается в том, что сторонние библиотеки часто связывают вызовы методов, чтобы thisзначение указывало на что-то полезное.

Например, внутри обработчика событий jQuery thisвы получите доступ к элементу DOM, к которому был привязан обработчик:

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

Но если мы используем стрелочную функцию, которая, как мы видели, не имеет собственного thisконтекста, мы получим неожиданные результаты:

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

Вот еще один пример с использованием Vue.js:

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

Внутри createdкрючка thisон привязан к экземпляру Vue, поэтому «Hello, World!» отображается сообщение.

Однако, если мы используем стрелочную функцию, она thisбудет указывать на родительскую область, у которой нет messageсвойства:

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

Стрелочные функции не имеют argumentsобъекта

Иногда вам может потребоваться создать функцию с неопределенным числом параметров. Например, предположим, что вы хотите создать функцию, которая перечисляет ваши любимые сериалы Netflix в порядке предпочтения. Однако вы пока не знаете, сколько серий вы собираетесь включить. JavaScript делает доступным объект arguments, объект, подобный массиву (однако, не полноценный массив), в котором хранятся значения, которые передаются функции при ее вызове.

Попробуйте реализовать эту функциональность с помощью стрелочной функции:

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')) 

При вызове функции, вы получите следующее сообщение об ошибке: Uncaught ReferenceError: arguments is not defined. Это означает, что argumentsобъект недоступен внутри стрелочных функций. На самом деле, замена стрелочной функции на обычную позволяет добиться цели:

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"]

Итак, если вам нужен argumentsобъект, вы не можете использовать стрелочные функции.

Но что, если вы действительно хотите использовать стрелочную функцию для воспроизведения той же функции? Единственное, что вы можете сделать, это использовать параметры отдыха ES6 (…). Вот как вы могли бы переписать свою функцию:

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

Вывод

Используя стрелочные функции, вы можете писать краткие однострочные строки с неявным возвратом и, наконец, забыть о старых хитростях для решения привязки thisключевого слова в JavaScript. Стрелка функция также отлично работает с методами массива, как.map(),.sort(),.forEach(),.filter()и.reduce(). Но помните, что стрелочные функции не заменяют обычные функции JS, и их нельзя использовать для всего.

Читайте также:  Python или Java: сравнение, что выбрать?
Оцените статью
bestprogrammer.ru
Добавить комментарий