Главная Категории Контакты Поиск

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

JavaScript Стрелочные функции. Введение.

JavaScript·28.03.2019·читать 6 мин 🤓·Автор: Alexey Myzgin

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

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

Например: у меня есть массив имен const names = ['Alex', 'Julia'];. Я хочу добавить is cool в конце двух имен.

const newNames = names.map(function (name) {
  return `${name} is cool`;
});

console.log(newNames); // "Alex is cool", "Julia is cool"

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

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

Первое, что ты делаешь это удаляешь слово function и добавляешь то, что называется жирной стрелкой. Это выглядит так: =>

const newNames = names.map((name) => {
  return `${name} is cool`;
});

console.log(newNames); // "Alex is cool", "Julia is cool"

Удаление скобок с одиночным параметром

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

const newNames = names.map((name) => {
  return `${name} is cool`;
});

console.log(newNames); // "Alex ic cool", "Julia ic cool"

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

Неявный возврат стрелочных функций

Что такое неявный возврат или implicit return? Давай для начала разберем, что такое явный возврат или explicit return.

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

Итак, если единственная цель стрелочной функции - вернуть что-то, то нет необходимости в слове return.

Наша трехстрочная функция с явным возвратом, теперь является однострочной функцией с неявным возвратом.

const newNames = names.map((name) => `${name} is cool`);

console.log(newNames); // "Alex is cool", "Julia is cool"

Мы сделали три вещи здесь:

  • удалили return;
  • перенесли всё на одну строчку;
  • удалили фигурные скобки.

Когда ты удаляешь фигурные скобки, это будет неявный возврат, который означает, что не нужно указывать return, что мы возвращаем ${name} is cool.

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

Если у тебя нет аргументов вообще - в приведенных выше примерах, очевидно, нам нужен аргумент - но если аргументов нет вообще, тебе нужно передать пустые скобки.

Сейчас мы просто вернем Alex is cool.

const newNames = names.map(() => `Alex is cool`);

console.log(newNames); // "Alex is cool", "Alex is cool"

Расширенный синтаксис

Есть несколько нюансов в расширенном синтаксисе, которые полезно знать.

Во-первых, если ты пытаешься использовать однострочный синтаксис с одним выражением, а возвращаемое значение является литералом объекта. Можно подумать, что это будет выглядеть так:

(name, age) => { name: name, age: age };

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

Чтобы указать, что вместо этого ты хочешь вернуть объект, необходимо обернуть его в скобки:

(name, age) => ({ name: name, age: age });

Область контекста выполнения

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

Контекст выполнения - это абстрактное понятие среды, в которой Javascript код оценивается и выполняется. Всякий раз, когда какой-либо код выполняется в JavaScript, он запускается в контексте выполнения.

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

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

const test = {
  name: "test object",
  createAnonFunction: function () {
    return function () {
      console.log(this.name);
      console.log(arguments);
    };
  },
  createArrowFunction: function () {
    return () => {
      console.log(this.name);
      console.log(arguments);
    };
  },
};

У нас есть простой объект test с двумя методами, каждый из которых представляет собой функцию, которая создает и возвращает анонимную функцию.

Разница в том, что в первом случае используется традиционное выражение функции, а во втором - стрелочная функция.

Однако, если мы запустим их в консоли с одинаковыми аргументами, то получим разные результаты.

const anon = test.createAnonFunction('hello', 'world');
const arrow = test.createArrowFunction('hello', 'world');
anon();
undefined
{}

arrow();
test
object { '0': 'hello', '1': 'world' }

У анонимной функции есть свой собственный контекст функции, поэтому при ее вызове отсутствует доступная ссылка на this.name объекта test и на аргументы, вызываемые при его создании.

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

Стрелочные функции всегда являются анонимными

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

Так что же такое анонимная функция? На самом деле, что такое именная функция?

Именная функция выглядит примерно так:

function sayMyName(name) {
  alert(`Hello ${name}`);
}

Преимущество использования именной функции состоит в том, что если у тебя есть трассировка стека (стек вызовов), и возникла ошибка, то нужно выяснить, где именно что-то пошло не так; номер строки на которой это произошло, не очень полезен, так что тебе нужно знать имя функции, в которой ошибка возникла.

Если ты используешь стрелочную функцию, то ты не можешь назвать их. Ни одна из наших стрелочных функций не имеет имени. Ты можешь, поместить ее в переменную, например:

const sayMyName = (name) => {
  alert(`Hello ${name}!`);
};

sayMyName("Alex");

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

Когда не нужно использовать стрелочные функции

Перед тем, как начать использовать стрелочные функции везде, нам нужно поболтать. Стрелочные функции не заменяют обычные функции. Так же, как Flexbox и floats, pixels и rems, более старая вещь всё еще сохраняет много полезного, потому что она работает не так, как новая.

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

Обработчики кликов

У нас есть большая кнопка Push me.

<style>
  button {
    font-size: 50px;
  }
  .on {
    background: yellow;
  }
</style>

<button id="push">Push me</button>

При нажатии на эту кнопку, мы переключаем класс on, который делает кнопку желтой. Когда нажимаешь на эту кнопку, то запускается следующая функция:

const button = document.querySelector("#push");
button.addEventListener("click", () => {
  this.classList.toggle("on");
});

Но если мы нажмем на кнопку, то получим ошибку в консоли: TypeError, cannot read property 'toggle' of undefined.

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

const button = document.querySelector("#push");
button.addEventListener("click", () => {
  console.log("this is", this); // this is window!
  this.classList.toggle("on");
});

Важно!

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

const button = document.querySelector("#push");
button.addEventListener("click", function () {
  console.log("this is", this); // current element
  this.classList.toggle("on");
});

В консоли this теперь наша кнопка, и она работает.

Методы объекта

Теперь, давайте посмотрим на следующий пример, когда нужно привязать метод к объекту.

const person = {
  points: 23,
  score: () => {
    this.points++;
  },
};

У нас есть метод score, и всякий раз, когда мы вызываем person.score, он должен добавить нам еще один балл, который в настоящее время равны 23.

Если мы запустим person.score(); два раза, у нас должно быть 25 баллов. Но у нас по прежнему 23. Почему?

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

Итак, давай сделаем то же самое с обычной функцией:

const person = {
  points: 23,
  score: function () {
    this.points++;
  },
};

Теперь всё работает.

Методы прототипа

В качестве третьего примера мы поговорим о том, когда нам нужно будет добавить метод-прототипа.

class Developer {
  constructor(name, dev) {
    this.name = name;
    this.dev = dev;
  }
}

Например, у нас есть класс. Если ты еще не слышал о классах, то достаточно знать, что это способ для нас создавать новых разработчиков.

Также, есть конструктор класса, куда, когда ты вызываешь new Developer, мы передаем имя человека (name), а также, чем он занимается (dev).

Мы создаем двух разработчиков:

const front = new Developer("Alex", "Frontend");
const back = new Developer("John", "Backend");

Вызвав их в консоль, ты увидишь, что front возвращается как Developer {name: "Alex", dev: "Frontend"}, и back вернется как Developer {name: "John", dev: "Backend"}.

Теперь, добавим метод-прототипа:

Developer.prototype.hello = () => {
  return `Hello, my name is ${this.name} and I am a ${this.dev} developer`;
};

Это позволяет нам сделать так, что даже после того, как эти разработчики были созданы, мы можем добавить методы ко всем из них. Итак, наш метод Developer.prototype.hello установлен, поэтому давай выведем в консоль: front.hello().

Несмотря на то, что мы добавили метод после создания разработчиков Developer, (поскольку мы добавили его в прототип) он доступен в каждом объекте, который был создан оттуда.

Что делает этот прототип, так это возвращает this.name и this.dev в строку.

Однако в нашем примере this.name и this.dev не определены. Почему?

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

Developer.prototype.hello = function () {
  return `Hello, my name is ${this.name} and I am a ${this.dev} developer`;
};

Теперь, если мы вызовем front.hello(), то он выведет Hello, my name is Alex and I am a Frontend developer, а если back.hello(), мы получаем Hello, my name is John and I am a Backend developer. Опять же, ты должен использовать обычную функцию для этого.

Заключение

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

Website, name & logo
Copyright © 2022. Alex Myzgin