Стрелочные функции

Стрелочные функции

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

Выгода №1: короткий синтаксис

Давайте посмотрим на обычную функцию:

function funcName(params) {
  return params + 2;
}
funcName(2);
// 4

Этот код указывает одну из двух причин для создания функций стрелок: короткий синтаксис. Точно такие же функции могут быть выражены как стрелочная функция в одной строку кода:

var funcName = (params) => params + 2
funcName(2);
// 4

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

(parameters) => { statements }

Если у нас нет параметров, мы выражаем стрелочную функцию следующим образом:

() => { statements }

Если у вас только один параметр, скобки необязательны:

parameters => { statements }

Наконец, если вы возвращаете выражение, вы удаляете фигурные скобки:

parameters => expression
// это эквивалентно:
function (parameters){
 return expression;
}

Хорошо, теперь вы знаете синтаксис, как насчет примера? Откройте консоль разработчика Chrome (Windows: Ctrl + Shift + J) (Mac: Cmd + Option + J) и введите следующее:

var double = num => num * 2

Как вы видите, мы назначаем переменной double стрелочную функцию. Функция имеет один параметр, число. Поскольку есть только один параметр, мы опустили скобки вокруг параметра. Так как мы хотим вернуть значение num * 2, мы также опустили скобки вокруг возвращаемого выражения. Давайте вызовем функцию и увидим ее в действии:

double(2);
// 4
double(3);
// 6

Выгода №2: нет привязки this

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

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

Пример должен прояснить В консоли можно создать функцию-конструктор, а затем создать ее экземпляр:

function Counter() {
 this.num = 0;
}
var a = new Counter();

Как вы догадываетесь, с помощью функции-конструктора значение параметра this связано с вновь создаваемым объектом, в данном случае, с объектом a. Вот почему мы можем выполнить console.log a.num и получить 0

console.log(a.num);
// 0

Что, если мы хотим увеличивать значение a.num каждую секунду? Мы можем использовать функцию setInterval(). setInterval() - это функция, которая вызывает другую функцию после заданного количества миллисекунд. Давайте добавим её в нашу функцию Counter:

function Counter() {
 this.num = 0;
 this.timer = setInterval(function add() {
   this.num++;
   console.log(this.num);
 }, 1000);
}

Код выглядит так же, как и раньше, за исключением того, что мы добавили переменную this.timer и установили ее равной нашей функции setInterval. Каждые 1000 миллисекунд (одна секунда) код будет запущен. this.num будет увеличиваться на единицу, тогда он будет записан в консоль. Давайте попробуем. В консоли снова создайте экземпляр Counter:

var b = new Counter();
// NaN
// NaN
// NaN
// ...

Как вы видите, функция будет выполняться каждую секунду. Но результат не тот, который мы ожидаем. NaN (Not a Number). Итак, что пошло не так? Во-первых, во-первых, прекратите надоедать интервалом, запустив:

clearInterval(b.timer);

Продолжим. Наша функция setInterval не вызывается в объявленном объекте. Он также не вызывается с ключевым словом new (только как функция Counter()). И, наконец, мы не используем call, bind или apply. setInterval - это обычная функция. На самом деле, значение this в setInterval привязано к глобальному объекту! Давайте протестируем эту теорию, сделав лог значения this:

function Counter() {
  this.num = 0;
  this.timer = setInterval(function add() {
    console.log(this);
  }, 1000);
}
var b = new Counter();

Как вы увидите, объект window вызывается каждую секунду. Очистите интервал, запустив:

clearInterval(b.timer);

Вернемся к нашей первоначальной функции. Она регистрировала NaN, потому что this.num ссылался на свойство num объекта window (window.num, которого не существует), а не на объект b (b.num), который мы только что создали.

Итак, как мы это исправим? Стрелкой! Нам нужна функция, которая не связывает this. С помощью стрелочной функции, this сохраняет значение из контекста. Давайте возьмем нашу оригинальную функцию Counter и заменим наш setInterval стрелочной функцией.

function Counter() {
 this.num = 0;
 this.timer = setInterval(() => {
   this.num++;
   console.log(this.num);
 }, 1000);
}
var b = new Counter();
// 1
// 2
// 3
// ...

Как вы увидите, консоль начнет регистрировать увеличивающиеся числа - это работает! Исходное this созданное конструктором функции счетчика, сохраняется. Внутри функции setInterval this все еще связано с нашим вновь созданным объектом b!

Вы можете очистить интервал с помощью:

clearInterval(b.timer);

Для доказательства концепции мы можем снова попытаться зарегистрировать this изнутри нашей стрелочной функции. Мы создадим переменную, которая называется that в нашей функции Counter. Затем мы выводим true, если значение этого параметра в нашей функции setInterval равно значению this (that) родительской функции Counter:

function Counter() {
 var that = this;
 this.timer = setInterval(() => {
   console.log(this === that);
 }, 1000);
}
var b = new Counter();
// true
// true
// ...

Как и ожидалось, лог срабатывает каждый раз! Снова очистите интервал с помощью:

clearInterval(b.timer);

Выводы

Надеемся, эта статья помогла вам увидеть две основные преимущества стрелочных функций:

  1. Более короткий синтаксис
  2. Нет привязки this

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