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

В JavaScript создавать функцию кроме классического способа (Function Declaration) можно ещё посредством:

  • Function Expression;
  • Arrow Function.

Создание функции с использованием синтаксиса Function Expression

Function Expression - это объявление функции, которая является частью какого-либо выражения (например, присваивания).

const имя_функции = function(параметры) {
  // инструкции
};

Например, создание функции sum с использованием «классического» синтаксиса (Function Declaration) и Function Expression:

// Function Declaration
function sum(num1, num2) {
  return num1 + num2;
};

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

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

//вызов функции с передачей её 2 аргументов (чисел 7 и 4)
sum(7, 4);

При объявлении функции посредством Function Expression дополнительно указывать имя функции после ключевого слова function не нужно.

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

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

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

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

const factorial = function(num) {
  if (num <= 1) {
    return 1;
  }

  return arguments.callee(num - 1) * num;
};

Самовызывающаяся функция (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

Стрелочные функции (arrow function)

Стрелочная функция (arrow function) - это современный синтаксис для создания функций, который появился с приходом ES6 (ES 2015). Он позволяет записать её более кратко по сравнению с синтаксисом Function Expression.

Базовый синтаксис стрелочной функции:

(argument1, argument2, ... argumentN) => {
  // тело функции
}

Например, функция возвращающая среднее арифметическое двух чисел:

// Function Expression
const average = function(num1, num2) {
  return (num1 + num2) / 2;
}

// Стрелочная функция
const average = (num1, num2) => {
  return (num1 + num2) / 2;
}

В этом примере мы создали стрелочную функцию с двумя параметрами num1 и num2, которая вычисляет выражение (num1 + num2) / 2 и возвращает его результат.

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

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

Пример, в котором создадим стрелочную функцию, возвращающую массив определённой длины, заполненный случайными числами от 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 fillArr = numElements => {
  ...
};

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

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

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

До появления стрелочных функций каждая функция имела свой this (контекст в котором она выполнялась).

Например, в функции-конструкторе Timer в setInterval контекст this указывал не на этот объект, а на window, т.к. данная функция является его методом:

const Timer = function () {
  // здесь this - это ссылка на этот объект
  this.counter = 0;
  setInterval(function () {
    // здесь this - это window
    console.log(this.counter++);
  }, 1000);
};

const timer1 = new Timer();

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

const Timer = function () {
  this.counter = 0;
  // сохраняем текущий контекст в that
  const that = this;
  setInterval(function () {
    // используем that, которая указывает на этот объект
    console.log(that.counter++);
  }, 1000);
};

const timer1 = new Timer();

Стрелочная функция не содержит собственный контекст this. Значение this в стрелочной функции определяется снаружи, т.е. из окружающего её контекста.

const Timer = function () {
  // здесь this - это ссылка на этот объект
  this.counter = 0;
  setInterval(() => {
    // здесь this тоже указывает на этот контекст, т.к. берётся снаружи
    console.log(this.counter++);
  }, 1000);
};

const timer1 = new Timer();

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

// ...otherNums - rest парамтеры
const sum = (...otherNums) => {
  let result = 0;
  for (let num of otherNums) {
    if (typeof num === 'number') {
      result += num;
    }
  }
  return result;
};

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

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

Отличия между Function Declaration, Function Expression и Arrow Function:

Function Declaration Function Expression Arrow Function
Вызов функции? По имени, после которого нужно указать круглые скобки:
// объявление функции square
function square(a) {
  return a * a;
}

// вызов функции square
console.log(square(5)); // 25
По имени переменной, после которой следует указать круглые скобки:
// объявление функции square
const square = function (a) {
  return a * a;
};

// вызов функции square
console.log(square(5)); // 25
По имени переменной, после которой следует указать круглые скобки:
// объявление функции square
const square = a => a * a;
};

// вызов функции square
console.log(square(5)); // 25
Всплытие (hoisting)? Функции Function Declaration всплывают (hoisting), их можно вызывать до объявления:
// вызываем функцию square до её объявления
console.log(square(7)); // 49

function square(a) {
  return a * a;
}
Функции Function Expression нельзя использовать до объявления. Если функция сохранена в переменную, созданную с помощью ключевого слова var, то в этом случае поднимается только сама переменная. Если сохранить функцию в const или let, то и переменная в данном варианте всплывать не будет.
// ошибка при вызове функции
console.log(square(7)); // Uncaught ReferenceError: Cannot access 'square' before initialization

const square = function (a) {
  return a * a;
};

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

var square;
// на этом этапе переменная square имеет значение undefined
console.log(square(7)); // Uncaught TypeError: square is not a function
square = function (a) {
  return a * a;
};
Поведение стрелочной функции аналогично Function Expression.
Видимость функции вне блока в строгом режиме ('use strict')? При использовании 'use strict' функция, объявленная как Function Declaration, будет видна только внутри блока, в котором она объявлена.
'use strict';
if (true) {
  function sum(a, b, c) {
    return a + b + c;
  }
  console.log(sum(4, 5, 4)); // 13
}

// произойдёт ошибка
console.log(sum(4, 5, 4)); // Uncaught ReferenceError: sum is not defined
В отличие от Function Declaration, доступ к функции можно получить вне блока, в котором она создана (но только, если она сохранена в переменную, созданную с помощью ключевого слова var):
'use strict';
if (true) {
  var sum = function (a, b, c) {
    return a + b + c;
  };
  console.log(sum(10, 20, 10));
}
// имеем доступ к функции sum
console.log(sum(4, 5, 4));
Поведение стрелочной функции аналогично Function Expression.