Стрелочные функции, которые появились в стандарте ES6 (ECMAScript 2015), призваны упростить написание кода и облегчить жизнь JavaScript-программисту. Они позволяют значительно сократить запись функции и по другому работают с контекстом в плане использования ключевого слова this
.
Синтаксис стрелочной функции в JavaScript
Синтаксис стрелочной функции предполагает, что есть 2 разновидности таких функций: с краткой или блочной формой записи.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//краткая форма записи //без аргументов () => console.log('Es6 arrow func'); //с аргументами (x,y) => x+y; //блочная форма записи (x, y) =>{ if(x>y) return x; return y; } |
Ни в краткой, ни в блочной форме нельзя переносить стрелку на новую строку.
Давайте рассмотрим различные примеры стрелочных функций. Будем это делать, используя синтаксис, привычный в ES5 и ранее, а затем - его аналог в виде стрелочной функции ES6.
Начнем разбор синтаксиса с очень простой функции, которая выводит диалоговое окно alert()
и не принимает никаких параметров. Для того чтобы иметь возможность вызвать функцию, необходимо перед ее вызовом дать ей имя в виде переменной.
1 2 3 4 5 6 7 8 9 10 11 |
//обычная функция let hello = function(){ alert("Hello"); } hello(); //синтаксис ES6 let helloES6 = () => alert("Hello from arrow function"); helloES6(); |
В примере вы вызовете эти функции при клике на соответствующей кнопке.
Если посмотреть на синтаксис стрелочной функции по сравнению с привычной нам, то можно увидеть, что они похожи, особенно если сравнивать синтаксис function expression
. Нужно просто убрать ключевое слово function
и фигурные скобки, а также добавить после круглых скобок оператор =>
, благодаря которому функции в ES6 и получили название стрелочных. Сразу оговорюсь, что удаление фигурных скобок работает только для функций с одной строкой кода.
Немного модифицируем вывод сообщения. Теперь мы будем отображать некую строку в <p id="mes">...</p>
. Запускать функции будем по клику на кнопке. Для того, чтобы увидеть отличия, зададим разный текст для параметра message
, а в самой функции укажем в стилях разный цвет для выводимого текста.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<script> function printMessage(message){ mes.innerHTML = "<strong style='color: green'>"+ message+ "</strong>"; } let printMesES6 = (message) => mes.innerHTML = "<span style='color: red'>"+ message + "</span>"; </script> <p id="mes">Здесь выводим сообщение</p> <button class="btn" onclick="printMessage('Работает обычная функция')">Сообщение</button> <button class="btn btn-primary" onclick="printMesES6('Салют из стрелочной функции ES6')"> Сообщение в формате ES6</button> |
Тестируем пример:
Здесь выводим сообщение
Видим, что функции похожи, только для стрелочной отсутствует ключевое слово function
и фигурные скобки. Параметр message мы передали в круглых скобках и в первой, и во второй функции. На самом деле в стрелочной функции мы могли обойтись даже без круглых скобок для параметра, если он один:
1 |
let printMesES6 = message => mes.innerHTML = "<span style='color: red'>"+ message + "</span>"; |
Теперь рассмотрим функцию, которая возводит передаваемое число в квадрат. В ней у нас не только появился параметр, но есть ещё и возвращаемое значение:
1 2 3 4 5 6 7 8 9 |
function sq(x){ return x*x; } document.write('<p>20<sup>2</sup> = ' + sq(20) + '</p>'); //ES6 let sq_es6 = x => x*x; document.write('<p>25<sup>2</sup> = ' + sq_es6(25) + '</p>'); |
Как вы видите, в стрелочной функции мы обошлись и без круглых скобок, и без ключевого слова return
.
Внимание! Если аргументов у функции нет, пустые круглые скобки при ее вызове обязательны!
Давайте посмотрим на результат. Недоверчивые могут проверить на калькуляторе:
Теперь рассмотрим, как можно вернуть с помощью 2-х разных функций литерал объекта:
1 2 3 4 5 6 7 8 |
function getInfo(){ return {name: "Василий", age: 24} } let getUserInfo = () => {name: "Иван", age: 26} //!! ошибка console.log(getInfo()); console.log(getUserInfo()); |
Если в этом случае мы оставим одни фигурные скобки, то получим в консоли ошибку:
Поэтому исправляем синтаксис стрелочной функции, добавив круглые скобки вокруг фигурных:
1 |
let getUserInfo = () => ({name: "Иван", age: 26}) |
Видим теперь результат в консоли без ошибок:
Рассмотрим еще один вариант стрелочной функции, в которой у нас есть не одна, а две или более строки кода:
Такая функция имеет вид блока кода, поэтому ее тело обязательно помещается в фигурные скобки.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let minMax = function (x, y) { if(x>y) return x; return y; } //ES6 let minMaxEs6 = (x, y) => { if(x>y) return x; return y; } console.log(minMax(1290, 10340)); console.log(minMaxEs6 (12, -340)); |
Смотрим, что получится (заменяем console.log()
на document.write()
):
Ключевое слово this
и контекст вызова функции
Камнем преткновения в JavaScript всегда было обращение к ключевому слову this
внутри объектов и обработчиков событий при вызове метода setTimeout()
для выполнения отложенного действия.
Стрелочные функции не имеют своего this
, поэтому берут его значение из окружающего контекста (так называемое лексическое значение). Это означает, что this
используется из кода, содержащего стрелочную функцию. Все остальные функции в JavaScript получают значение this
динамически в зависимости от того, какой объект вызывает эту функцию.
Если рассматривать использование setTimeout()
внутри обработчика события, то мы получим ошибку при использовании такого кода:
1 2 3 4 5 6 7 8 9 10 11 12 |
var trigger = document.getElementById('trigger'); trigger.addEventListener('click', function() { console.log('onclick', this); // this = <div id="trigger">...</div> setTimeout(function() { console.log('setTimeout', this);// this = window this.nextElementSibling.classList.toggle('on'); this.textContent = this.textContent.indexOf('Отобразить') >-1 ? 'Скрыть блок':'Отобразить блок'; }, 1000) }); |
Посмотрите на скриншот консоли браузера:
Проблема в том, что в данном случае this
меняется в зависимости от того, какой объект позволяет использовать свой метод. На скриншоте выше видно, что при клике на объекте контекстом this
будет div
, а затем при вызове метода setTimeout()
- глобальный объект window
, которому этот метод, собственно, и принадлежит.
Заменим привычную функцию на стрелочную при вызове метода setTimeout()
и получим другой результат (дождитесь, пока пройдет секунда, заданная в setTimeout()
):
Код теперь такой:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var trigger = document.getElementById('trigger'); trigger.addEventListener('click', function() { console.log('onclick', this); setTimeout( () => { console.log('setTimeout', this); this.nextElementSibling.classList.toggle('on'); this.textContent = this.textContent.indexOf('Отобразить') >-1 ? 'Скрыть блок': 'Отобразить блок'; }, 1000) }); |
Загляните сами в консоль - там и в первом, и во втором случае будет <div id="trigger"></div>
.
Вторым способом, кроме использования стрелочной функции, является применение метода bind()
для связывания this
и определенного объекта при вызове setTimeout()
или обработке событий.
Внимание: не стоит использовать стрелочную функцию в качестве обработчика события, т.к. this
она возьмет из ближайшего окружения, и результат будет не таким, как вы ожидаете (см. пример ниже).
Метод объекта и this
в стрелочной функции
Еще выгоду от использования this
в стрелочной функции можно увидеть при использовании некоторых функций при создании объектов. Например, у нас есть такой код в стандарте ES5 и ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//"use strict"; function Pizza (name, size){ this.name = name; this.size = size; } Pizza.prototype.getPizza = function(){ console.log('this in getPizza: ', this); return this.size.map(function(size){ console.log('this in size.map(): ', this); return this.name + ' размером ' + size; }) } var margaret = new Pizza('Маргарита', ['30cм', '70см', '1м']); console.log(margaret.getPizza()); |
В консоли мы увидим, что у нас this.name
либо отображает объект Window
, либо воспринимается как undefined
в строгом режиме (раскомментируйте строку //"use strict";
, чтобы посмотреть)
Ранее для выходя из этой ситуации использовалась дополнительная переменная для сохранения текущего значения this
, например, _this
, that
или self
. Код перепишем так:
1 2 3 4 5 6 7 8 9 10 11 |
Pizza.prototype.getPizza = function(){ var self = this; console.log('this in getPizza: ', this, ', self:', self); return this.size.map(function(size){ console.log('this in size.map(): ', this, ', self:', self); return self.name + ' размером ' + size; }) } var margaret = new Pizza('Маргарита', ['30cм', '70см', '1м']); |
В консоли увидим уже другой результат: значения для this
и self
будут отличаться.
Второй способ решения этой проблемы - в использовании this
, который можно задать для некоторых методов массива вроде forEach()
, map()
, filter()
, some()
, every()
в качестве второго, необязательного аргумента после вызова callback
-функции.
Переписываем код еще раз:
1 2 3 4 5 6 7 8 9 10 11 12 |
Pizza.prototype.getPizza = function(){ console.log('this in getPizza: ', this); return this.size.map(function(size){ console.log('this in size.map(): ', this); return this.name + ' размером ' + size; }, this); } var margaret = new Pizza('Маргарита', ['30cм', '70см', '1м']); console.log(margaret.getPizza()); |
Получаем четкое обращение к нашему объекту Pizza
на каждом этапе:
Такой же вариант в консоли мы получим, использовав стрелочную функцию. Уберем из нее вывод console.log()
для уменьшения кода. Наша функция возьмет контекст объекта Pizza и избавит нас от мучений. Результирующая функция будет такой:
1 2 3 4 5 6 7 8 |
Pizza.prototype.getPizza = function() { console.log('this in getPizza: ', this); return this.size.map((size) => this.name + ' размером ' + size ); } var margaret = new Pizza('Маргарита', ['30cм', '70см', '1м']); console.log(margaret.getPizza()); |
Просто и красиво, не так ли? Все-таки, краткость - сестра таланта, а стрелочные функции - любимчики JS-программистов.
Стрелочная функция как метод класса
Классы в JavaScript появились также в стандарте ES6 (ECMAScript2015), поэтому логично было бы попробовать использовать стрелочные функции внутри класса в качестве одного из методов (но не в качестве конструктора).
Создадим простой класс User, в ктором используем стрелочную функцию для вывода информации о пользователе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class User { constructor(name = 'Неи', role = 'пользователь') { this.name = name; this.role = role; } printName = () => { document.write(`<p>${this.name} является ${this.role}ом</p>`); } } const admin = new User('Игорь', 'админ'); admin.printName(); const accountant = new User('Наталья', 'бухгалтер'); accountant.printName(); |
Поскольку метод printName()
объявлен внутри класса, то и ключевое слово this
будет относится к любому экземпляру данного класса, созданного с помощью конструктора new User()
.
Посмотрим на пример в действии:
Следует отметить, что стрелочные функции часто используются в таких библиотеках, как React или Vue.js, т.к. используют контекст класса.
Использование стрелочных функций в качестве callback-ов
Callback
-функции, или функции обратного вызова, очень часто сейчас используются для работы с массивами, а точнее с их методами forEach()
, map()
, reduce(). Лучше всего видно сокращение количества кода в тех методах, которые что-либо возвращают. Например, нам нужно записать в отдельный массив длину строк элементов существующего массива. Сравните, насколько короче будет запись в формате стрелочной функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let flowers = ['незабудка', 'тюльпан', 'роза', 'ромашка', 'гербера']; console.log( flowers.map(function(fl) { return fl.length }) ); console.log( flowers.map((fl) => fl.length) ); //или вывод на экран с помощью forEach document.write('<ul>'); flowers.forEach(function(fl) { document.write('<li>'+fl+'</li>'); }); flowers.forEach( (fl) => document.write('<li style="color: #00b6ff">' + fl + '</li>') ); document.write('<ul>'); |
Посмотрим на вывод массива с помощью array.foEach()
. Работа старого и нового синтаксиса отличается цветом текста.
Чего нельзя делать с помощью стрелочных функций:
- Стрелочные функции нельзя использовать в качестве конструкторов объектов, т.е. с ними нельзя использовать оператор
new
:
12let Message = message => console.log( message);let warning = new Message('Warning'); //будет ошибка
В консоли выведется сообщение:
- Нельзя использовать стрелочные функции в виде метода или прототипа объекта.
- Нельзя применить такие методы, как
Message.bind()
,Message.call()
,Message.apply()
, т.к. в стрелочных функциях мы не можем изменить значениеthis
, которое в них всегда берется из контекста. - В стрелочных функциях нет массива
arguments
- Нельзя использовать стрелочные функции напрямую для обработки событий в том случае, если в функции вы обращаетесь к объекту через ключевое слово this:
12345<div id="element">Клик по тексту сменит его цвет</div><script>element.addEventListener('click', function(){this.style.color = "violet"});element.addEventListener('click', () => {this.style.color = "blue"});</script>
Клик по тексту сменит его цвет
При клике на элементе вы не увидите синего текста, т.к. стрелочная функция выдаст нам ошибку:
Если же вы будете использовать другое обращение к элементу или делегирование событий черезevent.target
, или при использовании объекта события event и его свойств или методов, то стрелочные функции будут работать вполне корректно. Например:
12345const square = document.createElement('div');square.classList.add('square');square.addEventListener('mouseover', () => setColor(square));square.addEventListener('mouseout', () => removeColor(square));inner.appendChild(square)
Пример можно посмотреть в отдельной вкладке или в статье "Создание html-элементов в JavaScript". - Стрелочные функции, в отличие от обычных, не имеют своего
this
и значенийsuper
.