Функциональные выражения и стрелочные функции в JavaScript

Функциональные выражения

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

Ключевое слово function также применяется для определения функций в выражениях.

Функциональное выражение (от английского Function Expression) очень похоже на обычное объявление функции:

function(a, b) {
  const sum = a + b;
  return sum;
}

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

function funcDeclaration() {
  return 'Обычное объявление функции';
}

const funcExpression = function() {
  return 'Функциональное выражение';
}

Здесь функциональное выражение мы присвоили переменной funcExpression. В итоге, у функционального выражения по сути будет имя (название переменной). Затем, используя эту переменную мы можем вызвать данную функцию:

funcExpression();

В этом примере присвоим переменной sum функциональное выражение. А затем вызовем данную функцию, используя эту переменную:

const sum = function(num1, num2) {
  return num1 + num2;
};

// вызов функции, используя переменную sum
sum(7, 4);

Если Вы хотите внутри тела функции сослаться на эту же функцию, то можно создать именованное функциональное выражение:

const factorial = function factorialInner(num) {
  if (num <= 1) {
    return 1;
  }
  // использование factorialInner для вызова функции
  return factorialInner(num - 1) * num;
};

// выведем результат вызова функции factorial(5) в консоль
console.log(factorial(5)); // 120

// при попытке вызвать функцию по имени factorialInner получим ошибку
console.log(factorialInner(5)); // Uncaught ReferenceError: factorialInner is not defined

Вызов функции внутри себя используется для создания рекурсий. В этом примере именованное функциональное выражение имеет название factorialInner. По этому имени мы можем вызвать эту функцию внутри её же тела. Вне тела обратиться к этой функции по factorialInner нельзя.

При этом функциональное выражение присвоено переменной factorial, объявленной с помощью const. Используя эту переменную (т.е. factorial) мы можем вызвать данную функцию.

Но вызвать функцию внутри её тела можно не только по имени, но также с помощью свойства arguments.callee:

const factorial = function(num) {
  if (num <= 1) {
    return 1;
  }
  return arguments.callee(num - 1) * num;
};

Используйте NFE вместо arguments.callee

Это свойство устарело, при use strict оно не работает.

Единственная причина, по которой оно тут – это то, что его можно встретить в старом коде, поэтому о нём желательно знать.

Современная спецификация рекомендует использовать именованные функциональные выражения (NFE).

Ещё очень часто функциональное выражение используется как колбэк-функция. Т.е. как функция, которая передаётся в качестве аргумента в другую функцию. И эта другая функция где-то внутри себя вызывает эту callback функцию.

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

Пример использования функционального выражение в вызове другой функции:

setTimeout(function() {
  console.log('Сообщение, которое будет выведено в консоль через 1 секунду!');
}, 1000);

В этом примере используется стандартная функция setTimeout, которая доступна как в браузере, так и в Node.js. Она принимает на вход колбэк-функцию и количество миллисекунд, через которые нужно вызвать эту колбэк-функцию. Здесь нет смысла давать имя вот этой функции. Достаточно просто использовать анонимное функциональное выражение.

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

Стрелочные функции (от английского arrow function) – это функции, которые имеют немного другой более современный синтаксис. При создании стрелочных функций не используется ключевое слово function. Появились стрелочные функции в стандарте ECMAScript 2016 (7 редакции).

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

(num1, num2) => {
  const result = (num1 + num2) / 2;
  console.log(result);
}

У стрелочной функции нет имени. Начинается стрелочная функция сразу же с (), внутри которых при необходимости описываются параметры. Далее идёт специальная стрелочка, которая состоит из знака = и >. Этот специальный синтаксис как раз и делает эту функцию стрелочной. После этого идёт тело функции, внутри которого мы описываем действия, которая она будет выполнять при её вызове. В теле как в традиционной функции опционально с помощью return мы можем возвращать результат.

Как дать имя стрелочной функции? Точно также как анонимному функциональному выражению, т.е. путём его присваивания переменной.

const average = (num1, num2) => {
  const result = (num1 + num2) / 2;
  console.log(result);
}

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

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

average(7, 5); // 6

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

setTimeout(() => {
  console.log('Это сообщение будет выведено в консоль через 1 секунду!');
}, 1000);

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

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

Сокращение синтаксиса в стрелочных функциях

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

const greeting = name => {
  console.log(`Привет, ${name}`);
};

Но для удобства чтения стрелочной функции круглые скобки лучше не опускать:

const greeting = (name) => {
  console.log(`Привет, ${name}`);
};

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

const average = (num1, num2) => (num1 + num2) / 2;

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

Этот же примере без сокращённого варианта:

const average = (num1, num2) => {
  return (num1 + num2) / 2;
}

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

this в стрелочных функциях

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

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

const person = {
  firstName: 'Alexander',
  getFirstName: () => {
    return this.firstName;
  }
}

const firstName = person.getFirstName();
console.log(firstName); // undefined

В этом примере мы получили undefined, а не строку 'Alexander' как ожидали. Почему? Потому что при вызове функции person.getFirstName() она будет брать контекст снаружи, так как эта функция является стрелочной. Т.е. им не будет являться объект person. В случае, если мы этот выполняем в глобальной области видимости в браузере, то this в getFirstName будет являться объект window.

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

class Timer {
  constructor() {
    this.counter = 0;
    setInterval(() => {
      console.log(this.counter++);
    }, 1000);
  }
}

new Timer();

В этом примере в вызов setInterval() мы передали в качестве аргумента стрелочную функцию. Эта функция является методом объекта window. Но так как стрелочная функция не имеет собственного this, он берёт его снаружи, т.е. так как нам это нужно.

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

class Timer {
  constructor() {
    this.counter = 0;
    setInterval(function () {
      console.log(this.counter++);
    }, 1000);
  }
}

new Timer();

Как мы уже отмечали выше, setInterval() – это сокращенная запись от window.setInterval(), то this в setInterval() будет указывать на window.

Для того чтобы исправить этот код, можно, например, предварительно сохранить контекст в переменную that, а затем его использовать setInterval:

class Timer {
  constructor() {
    this.counter = 0;
    const that = this;
    setInterval(function () {
      console.log(that.counter++);
    }, 1000);
  }
}

new Timer();

Или воспользоваться методом bind():

class Timer {
  constructor() {
    this.counter = 0;
    setInterval(function () {
      console.log(this.counter++);
    }.bind(this), 1000);
  }
}

new Timer();

Из всех этих вариантов более простым будет с использованием стрелочной функции.

Стрелочные функции не подходят для callapply и bind. Они также не имеют массивоподобного объекта arguments. Получить все аргументы для которых нет параметров в этом случае можно с помощью оператора ...:

const sum = (...nums) => {
  let result = 0;
  for (let num of nums) {
    result += typeof num === 'number' ? num : 0;
  }
  return result;
};

console.log(sum(2, 5, -7, 11)); // 11

Ещё примеры

Пример, в котором создадим стрелочную функцию, возвращающую массив определённой длины, заполненный случайными числами от 0 до 9.

const fillArr = (numElements) => {
  const arr = [];
  for (let i = 0; i < numElements; i++) {
    arr.push(parseInt(Math.random() * 10));
  }
  return arr;
};

// вызов функции fillArr
console.log(fillArr(5)); // [1, 4, 6, 4, 9]

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

// () - необходимо указывать при отсутствии параметров
const result = numElements = () => {
  console.log('Привет, мир!');
};

result(); // 'Привет, мир!'

Самовызывающаяся функция (IIFE)

Самовызывающаяся функция или IIFE - это функция, которая вызывается сразу же как только до неё дойдёт интерпретатор кода.

Она используется для создания закрытой области видимости, и применяется в паттерне «модуль».

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

// num1 и num2 - параметры самовызывающейся функции
// 7 и 4 - аргументы самовызывающейся функции
(function (num1, num2) {
  console.log(num1 + num2); // 11
})(7, 4);

Паттерн «модуль»

const userInfo = (function() {
  // имя пользователя по умолчанию
  let name = 'Аноним';

  // возвращаем объект, состоящий из 2 функций
  return {
    getName: function () {
      return name;
    },
    setName: function (newName) {
      name = newName;
    },
  };
})();

console.log(userInfo.getName()); // 'Аноним'
console.log(userInfo.setName('Дима')); // 'Дима'
console.log(userInfo.getName()); // 'Дима'

// обратиться напрямую к переменной name нельзя, только через «публичные» методы
console.log(userInfo.name); // undefined

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

1. Если Вы создаёте функцию традиционным способом, то можете присвоить переменной (имени функции) новое значение:

function sum(a, b) {
  console.log(a + b);
}
sum(5, 3); // 8
sum = 7;
console.log(sum); // 7

Если функцию мы присвоим переменной, объявленной с помощью const, то затем присвоить новое значение этой переменной у нас уже не получится:

const sum = (a, b) => {
  console.log(a + b);
}
sum(5, 3); // 8
sum = 7; // Uncaught TypeError: Assignment to constant variable.

2. Функции, объявленные традиционным способом, всплывают, т.е. их можно использовать до их объявления:

sum(4, 3); // 7
function sum(a, b) {
  console.log(a + b);
}

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

sum(4, 3); // Uncaught ReferenceError: Cannot access 'sum' before initialization
const sum = (a, b) => {
  console.log(a + b);
}

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

Отличия между традиционным объявлением функции, функциональным выражением и стрелочной функцией:

1. Объявление и вызов функции

// традиционное объявление функции
function squareA(side) {
  return side * side;
};
// функциональное выражение, которое присвоено переменной square2
const squareB = function(side) {
  return side * side;
};
// стрелочная функция, которая присвоена переменной square3
const squareC = (side) => side * side;

// вызов традиционной функции осуществляется по имени
console.log(squareA(5)); // 25
// вызов функционального выражения и стрелочной функции осуществляется с использованием имени переменной
console.log(squareB(5)); // 25
console.log(squareC(5)); // 25

2. Всплытие (hoisting)

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

// функцию squareА можно вызвать до объявления
console.log(squareA(7)); // 49
console.log(squareB(7)); // Uncaught ReferenceError: Cannot access 'squareB' before initialization
console.log(squareC(7)); // Uncaught ReferenceError: Cannot access 'squareC' before initialization

function squareA(side) {
  return side * side;
};
const squareB = function(side) {
  return side * side;
};
const squareC = (side) => side * side;

3. Нельзя получить доступ к функции вне блока

В том числе и функции, объявленной традиционным способом, но только в строгом режиме 'use strict':

'use strict';
if (true) {
  function squareA(side) {
    return side * side;
  };
  // функциональное выражение, которое присвоено переменной square2
  const squareB = function (side) {
    return side * side;
  };
  // стрелочная функция, которая присвоена переменной square3
  const squareC = (side) => side * side;
}

console.log(squareA(9)); // Uncaught ReferenceError: squareA is not defined
console.log(squareB(9)); // Uncaught ReferenceError: squareB is not defined
console.log(squareC(9)); // Uncaught ReferenceError: squareC is not defined

Источник: itchief.ru

1 комментарий
  • Artem
    13 февраля 2023 в 16:40

    Хорошая статья

  • Написать комментарий