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

Александр Мальцев
Александр Мальцев
23K
9
Содержание:
  1. Создание функции с использованием синтаксиса Function Expression
  2. Самовызывающаяся функция (IIFE)
  3. Стрелочные функции (arrow function)
  4. Отличия между различными способами объявления функций
  5. Комментарии

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

В 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:

1. Вызов функции?

1.1. Function Declaration – по имени, после которого нужно указать круглые скобки:

// объявление функции square
function square(a) {
  return a * a;
}

// вызов функции square
console.log(square(5)); // 25

1.2. Function Expression – по имени переменной, после которой следует указать круглые скобки:

// объявление функции square
const square = function(a) {
  return a * a;
};

// вызов функции square
console.log(square(5)); // 25

1.3. Arrow Function – по имени переменной, после которой следует указать круглые скобки:

// объявление функции square
const square = a => a * a;
};

// вызов функции square
console.log(square(5)); // 25

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

2.1. Функции Function Declaration всплывают (hoisting), их можно вызывать до объявления:

// вызываем функцию square до её объявления
console.log(square(7)); // 49

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

2.2. Функции 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;
};

2.3. Поведение стрелочной функции аналогично Function Expression.

3. Видимость функции вне блока в строгом режиме ('use strict')?

3.1. При использовании '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

3.2. В отличие от 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));

3.3. Поведение стрелочной функции аналогично Function Expression.

Комментарии:

  1. Walks
    Walks
    15.12.2021, 21:33
    Добрый день. Подскажите, имеет ли анонимная функция собственное свойство «this», и объект «arguments»?
    Например:
    function First(){
      console.log(arguments)
    }
    
    Function.prototype.delay = function(delay) {
      return function (...args) {
        setTimeout(() => {
          this(...args); //Здесь "this" имеет значение анонимной функции или "Function.prototype.delay"?
        }, delay);
      }.bind(this); //Здесь "bind" привязывает анонимную функцию к "Function.prototype.delay"? Если да то зачем?
    };
    
    const second = First.delay(2000)
    
    second(1, 2, 3) //[Arguments] { '0': 1, '1': 2, '2': 3 }
    И еще, буду благодарен если проясните: почему в данном выражении из кода выше «setTimeout(() => {this(...args);» не теряется «this», ведь используется в стрелочной функции, да и после оператора «setTimeout» свойство «this» будет «undefine»?

    Спасибо за ответ.
    1. Александр Мальцев
      Александр Мальцев
      19.12.2021, 05:43
      this – это обращение к контексту. В функции this указывает на объект, в рамках которого она выполняется.
      Когда мы вызываем delay(2000) как метод First, то this будет указывать на First:
      const second = First.delay(2000)
      Т.е. в момент вызова delay ключевое слово this в данном коде везде будет указывать на First:
      Function.prototype.delay = function(delay) {
        return function (...args) {
          setTimeout(() => {
            this(...args); // здесь this указывает на First
          }, delay);
        }.bind(this); // здесь this указывает на First
      };
      Стрелочные функции не содержат собственный контекст this и не имеют собственного объекта arguments. Они его берут снаружи.
      Метод setTimeout по умолчанию выполняется в контексте объекта window, т.к. setTimeout – это сокращённая запись от window.setTimeout. Но если мы используем стрелочную функцию, то this будет браться снаружи и в данном примере нам не нужно будет привязывать необходимый нам контекст с помощью bind().
      Но, если мы бы не использовали стрелочную функцию, то bind() пришлось бы использовать, иначе бы this указывал бы на объект window:
      Function.prototype.delay = function(delay) {
        return function (...args) {
          setTimeout(function() {
            this(...args); //Здесь "this" имеет значение анонимной функции или "Function.prototype.delay"?
          }.bind(this), delay);
        }.bind(this); // здесь this указывает на контекст "bind" привязывает метод delay
      };
    2. Vadim
      Vadim
      22.10.2020, 10:54
      Есть задачка, в которой нужно создать функцию canBuyBeer. Эта функция должна параметром age принимать целое число и возвращать строку «You can buy beer», если переменная age больше или равна 18. Иначе возвращать строку «You can not buy beer».

      Наваял, но не работает:
      function canBuyBeer (age = 19) {
        if (age >= 18) {
          canBuyBeer = "You can buy beer";
        } else {
          canBuyBeer = "You can not buy beer";
        }
      }
      
      Подскажите, в чем ошибка? Как можно это выражение записать стрелочной функцией?
      Спасибо.
      1. Александр Мальцев
        Александр Мальцев
        22.10.2020, 12:22
        Вот так:
        const canBuyBeer = (age = 19) => {
          return age >= 18 ? 'You can buy beer' : 'You can not buy beer';
        }
        
      2. Александр
        Александр
        26.03.2017, 10:46
        Товарищи, помогите решить задачу. Пожалуйста, сижу и понять не могу как ее решить.
        Напишите функцию, которая будет принимать на вход строку с надписью для гравировки. На выход функция отдает стоимость для гравировки. Если строка пустая или равна undefined, то цена гравировки равна 0.

        Запишите в переменную строку текста, которую нужно будет выгравировать.
        Создайте функцию, принимающую в качестве аргумента строку.
        Воспользуйтесь методом split, чтобы получить массив слов, по аналогии с примером ниже:
        var string = 'Это строка из нескольких слов';
        var words = string.split(' ');
        console.log(words);
        
        Предусмотрите универсальный способ расчета стоимости гравировки от количества слов. Мы не ограничиваем клиентов по длине надписи.
        Верните в качестве результата работы вычисленную стоимость.
        Выведите результат работы функции в консоль в формате:
        Подарочная упаковка и гравировка:
        1. Владимир
          Владимир
          22.03.2017, 20:31
          А как же основное отличие function declaration от function expression(инициализация, когда можно их использовать в коде, обявление в блоках if, циклах и т.д. и видимость там) и можно про use strict упомятуть
          1. Александр Мальцев
            Александр Мальцев
            25.03.2017, 16:46
            Добавил информацию в статью.
            1. Александр Мальцев
              Александр Мальцев
              23.03.2017, 15:55
              Благодарю за советы, с помощью которых можно улучшить статью. На днях добавлю эту информацию.
            2. Иван
              Иван
              10.08.2016, 16:44
              Лучшая статья по этой теме.Спасибо!
              Войдите, пожалуйста, в аккаунт, чтобы оставить комментарий.