Добавить в корзинуПозвонить
Найти в Дзене
ovnoCod

Язык JavaScript - Объект функции, NFE

Вы думаете, что функция - это просто "кусочек кода, который можно вызвать"? Вы ошибаетесь. В JavaScript функция - это гражданин первого класса, объект высшего порядка, который можно присвоить переменной, передать как аргумент, вернуть из другой функции... и даже добавить к нему свойство. Да, вы не ослышались. У функций есть свойства. Как у обычных объектов. Сегодня мы разберём двойственную природу функций: с одной стороны - вызываемая конструкция, с другой - полноценный объект. И заодно познакомимся с NFE (Named Function Expression) - именованными функциональными выражениями, которые раскрывают скрытые возможности функций. В JavaScript функции - это объекты. Специальные объекты, которые можно вызывать, но всё же объекты. javascript function greet(name) {
return `Привет, ${name}!`;
}
// Функция - это объект
console.log(typeof greet); // "function"
console.log(greet instanceof Object); // true
// У функций есть свойства
greet.description = "Функция приветствия";
console.log(greet.des
Оглавление
картинка взята с ya.ru
картинка взята с ya.ru

Сущность и тень: Всё об объекте функции и именованных функциональных выражениях (NFE) в JavaScript

Вы думаете, что функция - это просто "кусочек кода, который можно вызвать"? Вы ошибаетесь. В JavaScript функция - это гражданин первого класса, объект высшего порядка, который можно присвоить переменной, передать как аргумент, вернуть из другой функции... и даже добавить к нему свойство.

Да, вы не ослышались. У функций есть свойства. Как у обычных объектов.

Сегодня мы разберём двойственную природу функций: с одной стороны - вызываемая конструкция, с другой - полноценный объект. И заодно познакомимся с NFE (Named Function Expression) - именованными функциональными выражениями, которые раскрывают скрытые возможности функций.

Часть 1. Функция - это объект

В JavaScript функции - это объекты. Специальные объекты, которые можно вызывать, но всё же объекты.

javascript

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

// Функция - это объект
console.log(typeof greet);
// "function"
console.log(greet instanceof Object);
// true

// У функций есть свойства
greet.description = "Функция приветствия";
console.log(greet.description);
// "Функция приветствия"

Что это значит? К функциям можно добавлять свойства, как к обычным объектам.

javascript

function counter() {
counter.count++;
console.log(`Вызвана ${counter.count} раз(а)`);
}

counter.count = 0;
// статическое свойство

counter();
// "Вызвана 1 раз(а)"
counter();
// "Вызвана 2 раз(а)"
counter();
// "Вызвана 3 раз(а)"

console.log(counter.count);
// 3

Часть 2. Свойства функции

2.1 name - имя функции

javascript

function sayHello() {}
console.log(sayHello.name);
// "sayHello"

const sayHi = function() {};
console.log(sayHi.name);
// "sayHi" (ES6 угадывает имя из переменной)

const greet = () => {};
console.log(greet.name);
// "greet"

// У анонимной функции нет имени
console.log((function() {}).name);
// "" (пустая строка)

2.2 length - количество параметров

javascript

function func1(a, b, c) {}
console.log(func1.length);
// 3

function func2(a, b = 10, c) {}
console.log(func2.length);
// 1 (только параметры до первого со значением по умолчанию)

function func3(...args) {}
console.log(func3.length);
// 0 (rest-параметр не считается)

2.3 prototype - для конструкторов

javascript

function User(name) {
this.name = name;
}

console.log(typeof User.prototype);
// "object"
// prototype используется только при вызове с new

Часть 3. Встроенные методы функций

3.1 call - вызов с указанием this

javascript

function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: "Анна" };

greet.call(user, "Привет", "!");
// "Привет, Анна!"

3.2 apply - как call, но аргументы массивом

javascript

greet.apply(user, ["Здравствуйте", "..."]); // "Здравствуйте, Анна..."

3.3 bind - создание новой функции с привязанным this

javascript

const boundGreet = greet.bind(user);
boundGreet("Привет", "!");
// "Привет, Анна!"

Часть 4. Функциональные выражения (Function Expression)

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

javascript

// Function Declaration (объявление)
function declared() {
console.log("Я поднимаюсь вверх");
}

// Function Expression (выражение)
const expressed = function() {
console.log("Я создаюсь только в момент выполнения");
};

Часть 5. Named Function Expression (NFE) - Именованное функциональное выражение

NFE - это когда у функционального выражения есть имя. Это имя доступно только внутри самой функции.

javascript

const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
// используем имя fact для рекурсии
};

console.log(factorial(5));
// 120
console.log(fact);
// ReferenceError: fact is not defined (имя недоступно снаружи)

Важно: Имя fact существует только внутри функции. Снаружи оно не видно.

5.1 Зачем нужно имя в NFE?

Причина 1: Надёжная рекурсия

javascript

// Проблема: функция может быть перезаписана
let fibonacci = function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};

const original = fibonacci;
fibonacci = function() { return 0; };

console.log(original(5));
// 0? (неправильно, рекурсия сломалась)
// Потому что внутри используется fibonacci, которая теперь новая

// Решение: NFE
let fibonacciSafe = function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
// всегда ссылается на правильную функцию
};

const originalSafe = fibonacciSafe;
fibonacciSafe = function() { return 0; };

console.log(originalSafe(5));
// 5 (работает!)

Причина 2: Улучшенная отладка

javascript

// Анонимная функция - стек вызовов неинформативен
const processUser = function(user) {
// сложная логика
throw new Error("Ошибка");
};

// Именованная функция - в стеке будет имя
const processUserNamed = function processUserImpl(user) {
// сложная логика
throw new Error("Ошибка");
};

// В консоли ошибки:
// Error: Ошибка
// at processUserImpl (script.js:5:9) - понятно, какая функция
// вместо "at (anonymous)"

Часть 6. Где имя функции берётся из?

6.1 Function Declaration

javascript

function myFunc() {}
console.log(myFunc.name);
// "myFunc"

6.2 Function Expression с именем

javascript

const myFunc = function named() {};
console.log(myFunc.name);
// "named"

6.3 Анонимное Function Expression

javascript

const myFunc = function() {};
console.log(myFunc.name);
// "myFunc" (ES6 угадал из переменной)

6.4 Стрелочная функция

javascript

const myFunc = () => {};
console.log(myFunc.name);
// "myFunc"

6.5 Свойство, присвоенное функции

javascript

const obj = {
myMethod() {}
};
console.log(obj.myMethod.name);
// "myMethod"

Часть 7. Различия между Function Declaration и NFE

-2

Часть 8. Продвинутые техники с объектом функции

8.1 Статические свойства (кэширование)

javascript

function factorial(n) {
if (n in factorial.cache) {
return factorial.cache[n];
}

let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}

factorial.cache[n] = result;
return result;
}

factorial.cache = {};

console.log(factorial(5));
// 120 (вычисляет)
console.log(factorial(5));
// 120 (из кэша)
console.log(factorial.cache);
// { "1": 1, "2": 2, "3": 6, "4": 24, "5": 120 }

8.2 Функция с памятью (мемоизация)

javascript

function memoize(fn) {
const cache = {};

const memoized = function(arg) {
if (arg in cache) {
console.log("Из кэша");
return cache[arg];
}
const result = fn(arg);
cache[arg] = result;
return result;
};

memoized.clearCache = function() {
Object.keys(cache).forEach(key => delete cache[key]);
console.log("Кэш очищен");
};

memoized.getCache = () => ({ ...cache });

return memoized;
}

const slowSquare = n => {
console.log("Вычисление...");
return n * n;
};

const fastSquare = memoize(slowSquare);
console.log(fastSquare(5));
// "Вычисление..." → 25
console.log(fastSquare(5));
// "Из кэша" → 25
fastSquare.clearCache();
console.log(fastSquare(5));
// "Вычисление..." → 25

8.3 Декораторы через свойства функции

javascript

function logged(fn) {
const wrapped = function(...args) {
console.log(`Вызов ${wrapped.name || "анонимной"} с аргументами:`, args);
const result = fn.apply(this, args);
console.log(`Результат:`, result);
return result;
};

// Копируем имя
wrapped.name = fn.name;
// Копируем другие свойства
Object.assign(wrapped, fn);

return wrapped;
}

function add(a, b) {
return a + b;
}
add.description = "Складывает два числа";

const loggedAdd = logged(add);
console.log(loggedAdd(2, 3));
// "Вызов add с аргументами: [2, 3]"
// "Результат: 5"
console.log(loggedAdd.description);
// "Складывает два числа"

Часть 9. Реальные кейсы

9.1 Фабрика счётчиков с NFE

javascript

function createCounter(initial = 0) {
let count = initial;

const counter = function increment() {
count++;
return count;
};

counter.decrement = function decrement() {
count--;
return count;
};

counter.reset = function reset() {
count = initial;
return count;
};

counter.getValue = function getValue() {
return count;
};

return counter;
}

const counter = createCounter(10);
console.log(counter());
// 11
console.log(counter());
// 12
console.log(counter.decrement());
// 11
console.log(counter.getValue());
// 11
console.log(counter.reset());
// 10

9.2 Рекурсивный обход с защитой от перезаписи

javascript

const traverseDOM = function traverse(node, callback) {
callback(node);

if (node.children) {
for (const child of node.children) {
traverse(child, callback);
// всегда вызывает правильную функцию
}
}
};

// Даже если кто-то перезапишет переменную, рекурсия не сломается
const original = traverseDOM;
traverseDOM = null;

// original всё ещё работает!
original(document.body, node => console.log(node.tagName));

9.3 Функция с метаданными

javascript

function createValidator(rules) {
const validator = function validator(data) {
for (const rule of validator.rules) {
if (!rule.check(data)) {
return { valid: false, error: rule.message };
}
}
return { valid: true };
};

validator.rules = rules;

validator.addRule = function(rule) {
validator.rules.push(rule);
return validator;
};

validator.removeRule = function(index) {
validator.rules.splice(index, 1);
return validator;
};

return validator;
}

const userValidator = createValidator([
{ check: d => d.name, message: "Имя обязательно" },
{ check: d => d.age >= 18, message: "Должен быть старше 18" }
]);

console.log(userValidator({ name: "Анна", age: 25 }));
// { valid: true }
console.log(userValidator({ name: "Борис", age: 15 }));
// { valid: false, error: "Должен быть старше 18" }

userValidator.addRule({ check: d => d.email, message: "Email обязателен" });
console.log(userValidator({ name: "Анна", age: 25 }));
// { valid: false, error: "Email обязателен" }

Часть 10. Подводные камни

Камень #1: Имя NFE не перезаписывается

javascript

let func = function named() {
console.log(named);
// function named() { ... }
};

named = 42;
// не влияет на внутреннее имя
console.log(typeof named);
// "number" (но внутри функции named всё ещё функция)

Камень #2: NFE и рекурсия с изменённой переменной

javascript

let factorial = function f(n) {
if (n <= 1) return 1;
return n * f(n - 1);
// использует f, а не factorial
};

const copy = factorial;
factorial = null;

console.log(copy(5));
// 120 (работает, потому что рекурсия через f)

Камень #3: Свойства функции не сохраняются при bind

javascript

function greet() {
console.log("Hello");
}
greet.custom = 42;

const bound = greet.bind({});
console.log(bound.custom);
// undefined (свойство потеряно)

Итог: Манифест объекта функции и NFE

  1. Функция - это объект - можно добавлять свойства.
  2. name и length - встроенные свойства функции.
  3. call, apply, bind - управление контекстом this.
  4. NFE (Named Function Expression) - const f = function g() {}.
  5. Имя NFE доступно только внутри - безопасная рекурсия.
  6. NFE защищает от перезаписи переменной - рекурсия не сломается.
  7. Имя помогает в отладке - стек вызовов становится понятнее.
  8. Статические свойства - для кэширования, мемоизации, метаданных.

Финальный тест (что выведет?):

javascript

const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};

console.log(factorial(4));
console.log(typeof fact);

const copy = factorial;
factorial = null;
console.log(copy(4));

function test() {}
test.value = 42;
console.log(test.value);
console.log(test.length);

Ответы: 24, "undefined", 24 (работает через fact), 42, 0 (нет параметров).

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