Синтаксис функции JavaScript
Синтаксис new Function в JavaScript: создание функций из строк во время выполнения, правила области видимости, отличия от eval и случаи применения.
В большинстве случаев функция создаётся через объявление, выражение или стрелочную функцию. Но в JavaScript есть ещё один способ: конструктор new Function, который строит функцию из строк во время выполнения программы. На этой странице рассматривается именно такой синтаксис — его точная форма, правила области видимости, которые удивляют большинство разработчиков, отличия от eval и узкий круг случаев, когда это правильный инструмент.
Синтаксис new Function
Синтаксис new Function позволяет создать функцию, параметры и тело которой передаются в виде строк. Поскольку тело — это просто текст до момента его разбора движком, можно собрать функцию, код которой не известен при написании программы — он появляется только в момент выполнения.
Общая форма выглядит так:
let func = new Function([arg1, arg2, ...argN], functionBody);Каждый аргумент является строкой. Первые аргументы задают имена параметров; последний аргумент всегда является телом функции.
Все имена параметров можно передать в одной строке через запятую — результат будет тем же:
Ключевое слово new здесь необязательно — Function('a', 'b', 'return a + b') даёт тот же результат — но форма new Function(...) является общепринятой и более понятной.
Зачем это нужно
Главное отличие new Function от обычного объявления состоит в том, что тело берётся из строки. Эта строка может прийти откуда угодно: из ответа сервера, шаблона, конфигурации пользователя или текста, собранного во время выполнения. Таким образом, синтаксис существует именно для тех случаев, когда код, который нужно выполнить, не существует на момент написания программы.
Область видимости: главный подводный камень
Это та деталь, которая застаёт всех врасплох. Функция, созданная с помощью new Function, не захватывает область видимости места, где она была создана. В отличие от обычного замыкания, её внешним лексическим окружением является глобальная область видимости, а не локальная.
function makeAdder() {
let outer = 100;
// This function tries to read `outer`...
return new Function('x', 'return x + outer');
}
const add = makeAdder();
add(5); // ReferenceError: outer is not definedОбычная функция, написанная таким же образом, спокойно замкнулась бы над outer. Версия с new Function не может этого сделать — она видит только свои параметры и глобальную область видимости:
Это сделано намеренно. Если бы new Function могла обращаться к локальным переменным, минификаторы (которые переименовывают outer в a, secret в b и т.д.) ломали бы любой код, ссылающийся на эти имена в виде строк. Ограничивая доступ глобальной областью видимости, язык обеспечивает безопасность минификации. Практический вывод: передавайте всё необходимое динамической функции через её аргументы — не рассчитывайте, что она сможет читать окружающие переменные.
Свойства динамической функции
Функция, созданная таким образом, является обычным объектом-функцией во всех остальных отношениях, но с одной особенностью — её свойство name всегда равно "anonymous":
Такое «безымянное» имя — одна из причин, по которым динамические функции сложнее читать в стектрейсах. Подробнее об этом — в заметке про отладку ниже.
new Function и eval
И new Function, и eval превращают строки в выполняемый код, однако ведут себя совершенно по-разному:
eval(str)выполняетstrв текущей области видимости, поэтому может читать и даже изменять соседние локальные переменные. Такая тесная связь затрудняет оптимизацию и упрощает злоупотребления.new Functionизолирована от локальной области видимости (как показано выше) и возвращает многократно используемую функцию, а не одноразовое вычисление.
В редком случае, когда действительно нужно выполнить код из строки, new Function является более безопасным вариантом из двух, поскольку её зона влияния ограничена глобальной областью видимости и явными параметрами.
Практические применения и примеры
Ниже представлен полный, запускаемый пример, который можно редактировать и выполнять прямо в браузере.
Углублённое понимание динамического создания функций
Динамическое создание функций в JavaScript с помощью синтаксиса new Function — это мощная техника, позволяющая разработчикам конструировать функции из строк кода во время выполнения. Она особенно полезна в сценариях, где выполняемый код не является статическим или не известен заранее: например, в приложениях, требующих высокой степени гибкости, или в ситуациях, когда скрипты генерируются или изменяются динамически. В этом разделе мы подробнее рассмотрим механику, преимущества и особенности динамического создания функций, приводя практические примеры для наглядности.
Механика динамического создания функций
Синтаксис new Function создаёт новый экземпляр функции. Аргументами конструктора new Function являются строки, представляющие параметры функции, за которыми следует строка, представляющая тело функции.
Это функционально эквивалентно объявлению функции традиционным способом, но с ключевым отличием — возможностью динамически собирать код функции во время выполнения.
Преимущества динамического создания функций
- Гибкость и настраиваемость: Динамическое создание функций обеспечивает высокую степень настраиваемости — функции могут генерироваться на основе пользовательского ввода, параметров конфигурации или других данных времени выполнения.
- Скриптинг и шаблонизация: Особенно полезно при реализации пользовательских скриптовых решений или шаблонизаторов, где логика шаблона должна вычисляться во время выполнения.
- Изоляция и безопасность: При осторожном использовании позволяет выполнять код в более контролируемой среде, потенциально изолируя динамически выполняемый код от контекста основного приложения.
Особенности и рекомендации
Несмотря на мощь динамического создания функций, оно несёт ряд рисков и требует соблюдения определённых правил:
- Безопасность: Основная проблема — безопасность. Поскольку код функции конструируется из строк, существует риск выполнения вредоносного кода, если входные данные не прошли должную обработку. Всегда валидируйте и очищайте входные данные, которые будут использоваться для генерации кода функции.
- Производительность: Динамически создаваемые функции могут работать медленнее своих статически объявленных аналогов, поскольку движок JavaScript должен разбирать строку тела функции каждый раз при создании новой функции. Используйте эту возможность взвешенно, особенно на критически важных с точки зрения производительности участках.
- Отладка: Отладка динамически генерируемых функций может быть сложнее, так как код не существует до момента выполнения. Присвоение осмысленных имён динамически создаваемым функциям помогает смягчить эту проблему.
- Ограничение лексической области видимости: Функции, созданные с помощью
new Function, не захватывают локальную область видимости места их определения. Они имеют доступ только к глобальным переменным и своим собственным параметрам. Это может приводить кReferenceError, если ожидается доступ к внешним переменным. (См. раздел об области видимости выше; передавайте данные через аргументы.)
Продвинутый пример: простой шаблонизатор
Для иллюстрации практического использования динамического создания функций рассмотрим реализацию простого шаблонизатора. Этот движок будет заменять заполнители в строке шаблона значениями из объекта данных — причём, что важно, данные передаются как аргумент, обходя ограничение области видимости.
Примечание: последовательность \${ экранирует синтаксис шаблонного литерала. Это предотвращает немедленное вычисление заполнителя ${expr}, гарантируя, что он передаётся как литеральная строка в тело генерируемой функции.
Этот пример демонстрирует не только гибкость динамического создания функций, но и важность тщательной конструкции и очистки входных данных во избежание угроз безопасности.
Резюме
Конструктор new Function создаёт функцию из строк во время выполнения:
let func = new Function([arg1, ..., argN], functionBody);Ключевые моменты:
- Последний аргумент всегда является телом функции; предшествующие аргументы задают имена параметров.
- Внешняя область видимости динамически созданной функции — глобальная, а не то место, где она была создана: она не может замыкаться над локальными переменными, поэтому передавайте данные через аргументы.
- В целом безопаснее
eval(который выполняется в текущей области видимости), однако оба вычисляют строки — поэтому никогда не передавайте им ничего, кроме надёжных, очищенных данных. - Используйте его для по-настоящему динамического кода — шаблонизаторов, изолированных вычислителей выражений, генерируемых во время выполнения обработчиков — и предпочитайте обычные функции или стрелочные функции везде, где это возможно.
Чтобы подробнее изучить смежные темы, см. Замыкания JavaScript, Область видимости переменных и Функциональные выражения.