Вы пишете систему мониторинга API: нужно считать сколько раз каждый эндпоинт был вызван, без изменения оригинальных функций. Решение — функции как объекты: можно навешивать свойства прямо на функцию, как на любой объект.
В JavaScript функции — это объекты первого класса. У них есть встроенные свойства (name, length) и возможность добавлять свои. Это открывает паттерны, недоступные в других языках.
typeof function() {} // 'function'
function f() {} instanceof Object // trueКаждая функция имеет свойство name — её имя:
// Function Declaration — имя очевидно
function greet(name) { return 'Привет, ' + name }
console.log(greet.name) // 'greet'
// Function Expression — JS выводит имя из переменной
const sayBye = function(name) { return 'Пока, ' + name }
console.log(sayBye.name) // 'sayBye'
// Arrow function — то же самое
const add = (a, b) => a + b
console.log(add.name) // 'add'
// Метод объекта
const obj = { greet() {} }
console.log(obj.greet.name) // 'greet'
// Без имени
console.log(function() {}.name) // '' (пустая строка)Свойство length возвращает количество параметров до первого rest-параметра или параметра со значением по умолчанию:
function f1(a, b, c) {}
console.log(f1.length) // 3
function f2(a, b = 0, c) {}
console.log(f2.length) // 1 (только a, до первого default)
function f3(a, ...rest) {}
console.log(f3.length) // 1 (только a, rest не считается)
// Полезно для метапрограммирования:
// можно узнать сколько аргументов ожидает функцияТак как функция — объект, на неё можно навесить любые свойства:
function processOrder(order) {
processOrder.callCount++
// ... обработка заказа
return order
}
processOrder.callCount = 0
processOrder({ id: 1 })
processOrder({ id: 2 })
console.log(processOrder.callCount) // 2Это не то же самое, что замыкание:
// Замыкание — переменная скрыта, недоступна снаружи
function makeCounter() {
let count = 0
return function() { return ++count }
}
// Свойство функции — данные открыты и доступны снаружи
function counter() { return ++counter.count }
counter.count = 0
// Можно читать и менять снаружи:
console.log(counter.count) // 0
counter.count = 100 // можно сбросить!Named Function Expression (NFE) — это function expression с именем:
// Обычный function expression (анонимный)
const factorial = function(n) {
return n <= 1 ? 1 : n * factorial(n - 1) // проблема: зависит от внешней переменной!
}
// NFE — имя доступно внутри самой функции
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1) // внутри можно вызывать по имени fact
}
console.log(factorial(5)) // 120
console.log(typeof fact) // 'undefined' — имя недоступно снаружи!Зачем NFE?
function fibonacci(n) {
if (fibonacci.cache[n] !== undefined) return fibonacci.cache[n]
if (n <= 1) return n
fibonacci.cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
return fibonacci.cache[n]
}
fibonacci.cache = {}
console.log(fibonacci(40)) // быстро, результаты кэшируются
console.log(Object.keys(fibonacci.cache).length) // количество кэшированных значенийОшибка 1: не инициализируют свойство функции до первого вызова
// Сломано: counter.count не существует при первом вызове
function counter() {
counter.count++ // NaN: undefined + 1
return counter.count
}
// counter.count = 0 ← забыли!
// Исправлено:
counter.count = 0
function counter() {
counter.count++
return counter.count
}Ошибка 2: NFE — имя доступно внутри, но не снаружи
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1)
}
console.log(factorial(5)) // 120 — OK
// console.log(fact(5)) // ReferenceError: fact is not defined
// fact — видно ТОЛЬКО внутри тела функцииОшибка 3: свойство функции vs замыкание
// Свойство функции — доступно снаружи, можно изменить
function counter() { return ++counter.count }
counter.count = 0
counter.count = 100 // внешний код может сломать состояние!
// Замыкание — скрыто, защищено
function makeCounter() {
let count = 0
return { inc: () => ++count, get: () => count }
}
// count нельзя изменить снаружиСчётчик вызовов как свойство функции и NFE-рекурсия факториала
// --- Пример 1: счётчик вызовов как свойство функции ---
function sendEmail(to, subject) {
sendEmail.callCount++
sendEmail.lastRecipient = to
// В реальном коде здесь была бы отправка письма
console.log(`Отправка письма на ${to}: "${subject}"`)
}
sendEmail.callCount = 0
sendEmail.lastRecipient = null
sendEmail('ivan@example.ru', 'Ваш заказ подтверждён')
sendEmail('maria@example.ru', 'Акция: скидка 20%')
sendEmail('ivan@example.ru', 'Заказ доставлен')
console.log('Отправлено писем:', sendEmail.callCount) // 3
console.log('Последний адресат:', sendEmail.lastRecipient) // 'ivan@example.ru'
console.log('Имя функции:', sendEmail.name) // 'sendEmail'
console.log('Параметров:', sendEmail.length) // 2
// --- Пример 2: NFE для безопасной рекурсии ---
// Проблема без NFE: если переменную переприсвоят — рекурсия сломается
let badFactorial = function(n) {
return n <= 1 ? 1 : n * badFactorial(n - 1)
}
const saved = badFactorial
badFactorial = null
try {
saved(5) // TypeError: badFactorial is not a function
} catch (e) {
console.log('Ошибка без NFE:', e.message)
}
// NFE решает проблему: имя 'fact' доступно внутри, не зависит от переменной
const goodFactorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1)
}
const savedGood = goodFactorial
// goodFactorial = null — даже если переприсвоить, savedGood работает
console.log('\n5! =', goodFactorial(5)) // 120
console.log('10! =', goodFactorial(10)) // 3628800
console.log('Имя NFE снаружи:', goodFactorial.name) // 'fact'
console.log('fact снаружи:', typeof fact) // 'undefined'Вы пишете систему мониторинга API: нужно считать сколько раз каждый эндпоинт был вызван, без изменения оригинальных функций. Решение — функции как объекты: можно навешивать свойства прямо на функцию, как на любой объект.
В JavaScript функции — это объекты первого класса. У них есть встроенные свойства (name, length) и возможность добавлять свои. Это открывает паттерны, недоступные в других языках.
typeof function() {} // 'function'
function f() {} instanceof Object // trueКаждая функция имеет свойство name — её имя:
// Function Declaration — имя очевидно
function greet(name) { return 'Привет, ' + name }
console.log(greet.name) // 'greet'
// Function Expression — JS выводит имя из переменной
const sayBye = function(name) { return 'Пока, ' + name }
console.log(sayBye.name) // 'sayBye'
// Arrow function — то же самое
const add = (a, b) => a + b
console.log(add.name) // 'add'
// Метод объекта
const obj = { greet() {} }
console.log(obj.greet.name) // 'greet'
// Без имени
console.log(function() {}.name) // '' (пустая строка)Свойство length возвращает количество параметров до первого rest-параметра или параметра со значением по умолчанию:
function f1(a, b, c) {}
console.log(f1.length) // 3
function f2(a, b = 0, c) {}
console.log(f2.length) // 1 (только a, до первого default)
function f3(a, ...rest) {}
console.log(f3.length) // 1 (только a, rest не считается)
// Полезно для метапрограммирования:
// можно узнать сколько аргументов ожидает функцияТак как функция — объект, на неё можно навесить любые свойства:
function processOrder(order) {
processOrder.callCount++
// ... обработка заказа
return order
}
processOrder.callCount = 0
processOrder({ id: 1 })
processOrder({ id: 2 })
console.log(processOrder.callCount) // 2Это не то же самое, что замыкание:
// Замыкание — переменная скрыта, недоступна снаружи
function makeCounter() {
let count = 0
return function() { return ++count }
}
// Свойство функции — данные открыты и доступны снаружи
function counter() { return ++counter.count }
counter.count = 0
// Можно читать и менять снаружи:
console.log(counter.count) // 0
counter.count = 100 // можно сбросить!Named Function Expression (NFE) — это function expression с именем:
// Обычный function expression (анонимный)
const factorial = function(n) {
return n <= 1 ? 1 : n * factorial(n - 1) // проблема: зависит от внешней переменной!
}
// NFE — имя доступно внутри самой функции
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1) // внутри можно вызывать по имени fact
}
console.log(factorial(5)) // 120
console.log(typeof fact) // 'undefined' — имя недоступно снаружи!Зачем NFE?
function fibonacci(n) {
if (fibonacci.cache[n] !== undefined) return fibonacci.cache[n]
if (n <= 1) return n
fibonacci.cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
return fibonacci.cache[n]
}
fibonacci.cache = {}
console.log(fibonacci(40)) // быстро, результаты кэшируются
console.log(Object.keys(fibonacci.cache).length) // количество кэшированных значенийОшибка 1: не инициализируют свойство функции до первого вызова
// Сломано: counter.count не существует при первом вызове
function counter() {
counter.count++ // NaN: undefined + 1
return counter.count
}
// counter.count = 0 ← забыли!
// Исправлено:
counter.count = 0
function counter() {
counter.count++
return counter.count
}Ошибка 2: NFE — имя доступно внутри, но не снаружи
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1)
}
console.log(factorial(5)) // 120 — OK
// console.log(fact(5)) // ReferenceError: fact is not defined
// fact — видно ТОЛЬКО внутри тела функцииОшибка 3: свойство функции vs замыкание
// Свойство функции — доступно снаружи, можно изменить
function counter() { return ++counter.count }
counter.count = 0
counter.count = 100 // внешний код может сломать состояние!
// Замыкание — скрыто, защищено
function makeCounter() {
let count = 0
return { inc: () => ++count, get: () => count }
}
// count нельзя изменить снаружиСчётчик вызовов как свойство функции и NFE-рекурсия факториала
// --- Пример 1: счётчик вызовов как свойство функции ---
function sendEmail(to, subject) {
sendEmail.callCount++
sendEmail.lastRecipient = to
// В реальном коде здесь была бы отправка письма
console.log(`Отправка письма на ${to}: "${subject}"`)
}
sendEmail.callCount = 0
sendEmail.lastRecipient = null
sendEmail('ivan@example.ru', 'Ваш заказ подтверждён')
sendEmail('maria@example.ru', 'Акция: скидка 20%')
sendEmail('ivan@example.ru', 'Заказ доставлен')
console.log('Отправлено писем:', sendEmail.callCount) // 3
console.log('Последний адресат:', sendEmail.lastRecipient) // 'ivan@example.ru'
console.log('Имя функции:', sendEmail.name) // 'sendEmail'
console.log('Параметров:', sendEmail.length) // 2
// --- Пример 2: NFE для безопасной рекурсии ---
// Проблема без NFE: если переменную переприсвоят — рекурсия сломается
let badFactorial = function(n) {
return n <= 1 ? 1 : n * badFactorial(n - 1)
}
const saved = badFactorial
badFactorial = null
try {
saved(5) // TypeError: badFactorial is not a function
} catch (e) {
console.log('Ошибка без NFE:', e.message)
}
// NFE решает проблему: имя 'fact' доступно внутри, не зависит от переменной
const goodFactorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1)
}
const savedGood = goodFactorial
// goodFactorial = null — даже если переприсвоить, savedGood работает
console.log('\n5! =', goodFactorial(5)) // 120
console.log('10! =', goodFactorial(10)) // 3628800
console.log('Имя NFE снаружи:', goodFactorial.name) // 'fact'
console.log('fact снаружи:', typeof fact) // 'undefined'Напиши функцию makeCounter(), которая возвращает функцию-счётчик. При каждом вызове счётчик увеличивает внутреннее значение на 1 и возвращает его. Функция-счётчик должна иметь два свойства: .reset() — сбрасывает счётчик в 0, и .getCount() — возвращает текущее значение без увеличения.
const counter = function() { counter.count++ }; counter.count = 0; counter.reset = () => { counter.count = 0 }; counter.getCount = () => counter.count