Function Expression

Функциональное выражение (Function Expression) — создание функции как части выражения, обычно через присваивание переменной. Не поднимается (no hoisting тела).

Зачем нужно

Function Expression позволяет создавать функции динамически, присваивать их переменным и передавать как значения. Это основа callback-паттерна и функционального стиля в JavaScript.

Где используется

Callback-функции, обработчики событий, условное создание функций, IIFE, фабричные функции.

Предпосылки

Function Declaration, Переменные

Синтаксис

Анонимное выражение

const greet = function(name) {
  return `Привет, ${name}!`;
};

console.log(greet('Алиса')); // "Привет, Алиса!"

Именованное выражение (Named Function Expression)

const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1); // имя fact доступно внутри
};

console.log(factorial(5)); // 120
// console.log(fact(5));   // ReferenceError — fact не видно снаружи

Отличие от Function Declaration

Hoisting

// Function Declaration — поднимается
console.log(declaredFn); // "Работаю!"
function declaredFn() {
  return 'Работаю!';
}

// Function Expression — НЕ поднимается
// console.log(expressionFn); // TypeError: expressionFn is not a function
const expressionFn = function {
  return 'Работаю!';
};

Сравнительная таблица

Свойство Declaration Expression
Hoisting Да (целиком) Нет (только переменная)
Имя обязательно Да Нет
Точка с запятой Не нужна Нужна (часть выражения)
Условное создание Ненадёжно Безопасно
Свойство .name Имя функции Имя переменной или имя NFE

Условное создание

// Безопасно — с Function Expression
let validator;
if (strictMode) {
  validator = function(value) {
    return value !== null && value !== undefined && value !== '';
  };
} else {
  validator = function(value) {
    return value != null;
  };
}

Named Function Expression (NFE)

NFE полезны для рекурсии и отладки:

// Имя доступно только внутри функции
const findNode = function search(tree, id) {
  if (tree.id === id) return tree;
  for (const child of tree.children || ) {
    const found = search(child, id); // рекурсия через внутреннее имя
    if (found) return found;
  }
  return null;
};

console.log(findNode.name); // "search" — видно в стеке

Свойство name

// Анонимная — имя берётся из переменной
const fn1 = function {};
console.log(fn1.name); // "fn1"

// Именованная — имя из NFE
const fn2 = function myName() {};
console.log(fn2.name); // "myName"

// В объекте — имя из ключа
const obj = {
  method: function {}
};
console.log(obj.method.name); // "method"

Паттерны использования

Callback

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(function(n) {
  return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]

// С именем для отладки
const filtered = numbers.filter(function isEven(n) {
  return n % 2 === 0;
});

Немедленный вызов (IIFE)

const module = (function {
  let counter = 0;
  return {
    increment { return ++counter; },
    getCount { return counter; }
  };
});

console.log(module.increment); // 1
console.log(module.increment); // 2

Фабрика функций

const createMultiplier = function(factor) {
  return function(number) {
    return number * factor;
  };
};

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

Стратегия через объект

const strategies = {
  add: function(a, b) { return a + b; },
  subtract: function(a, b) { return a - b; },
  multiply: function(a, b) { return a * b; },
};

function calculate(strategy, a, b) {
  return strategies[strategy](a, b);
}

console.log(calculate('add', 5, 3));      // 8
console.log(calculate('multiply', 5, 3)); // 15

var vs let/const и hoisting

// var — переменная поднимается как undefined
console.log(typeof fnVar); // "undefined" (не ошибка!)
// fnVar; // TypeError: fnVar is not a function
var fnVar = function { return 42; };

// let/const — TDZ, ошибка при обращении
// console.log(fnLet); // ReferenceError
let fnLet = function { return 42; };

Частые ошибки

1. Забытая точка с запятой

const a = function { return 1; }  // нет ;
// Следующая строка может быть интерпретирована как вызов!
(function { console.log('IIFE'); });
// Без ; движок может понять это как: a(function...)

2. Вызов до объявления

// Ожидаем hoisting, но его нет
// processData(data); // TypeError
const processData = function(data) {
  return data.map(item => item.value);
};

3. Потеря this в методе

const obj = {
  value: 42,
  getValue: function {
    return this.value;
  }
};

const fn = obj.getValue;
console.log(fn); // undefined — this потерян
// Решение: bind или arrow function

Практика

  1. Создай Function Expression и попробуй вызвать её до объявления
  2. Напиши Named Function Expression с рекурсией (обход дерева)
  3. Создай фабрику функций, которая возвращает валидатор
  4. Используй Function Expression как callback в Array.prototype.sort()
  5. Сравни .name у анонимной и именованной Function Expression

Связанные темы

Ресурсы


🎓 Источник: Функции, стрелочные функции, контексты

  • 📅 2018-09-27 · YouTube · ID: pn5myCmpV2U
  • Тезисы:
    • 4 способа объявить функцию в JS: function declaration, function expression, lambda (arrow), new Function
    • Function expression видна только после присваивания — нет hoisting как у declaration
    • new Function('a', 'b', 'return a + b') — почти eval, использовать не стоит
    • Если функция передаётся в аргумент или присваивается ключу объекта, и не имеет имени — name берётся из ключа/идентификатора
  • Цитата: «Это чуть ли не eval, это неприлично» (про new Function)