Функциональные выражения и стрелочные функции в 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();
Из всех этих вариантов более простым будет с использованием стрелочной функции.
Стрелочные функции не подходят для call
, apply
и 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Хорошая статья