JavaScript eval()
Узнайте, как функция eval() в JavaScript выполняет строку как код: синтаксис, возвращаемое значение, прямые и косвенные вызовы, риски безопасности и альтернативы.
JavaScript умеет превращать строку в выполняемый код во время выполнения программы. Встроенная функция eval() делает именно это: принимает строку, интерпретирует её как исходный код JavaScript и запускает. На этой странице объясняется, как работает eval(), в каких случаях она возвращает значение, почему она почти всегда является неверным выбором и что использовать вместо неё.
На этой странице рассматривается:
- Синтаксис
eval()и что она возвращает. - Реальные случаи использования (и почему большинство из них сегодня имеют более безопасные альтернативы).
- Проблемы безопасности и производительности, из-за которых
eval()опасна. - Более безопасные замены: конструктор
FunctionиJSON.parse(). - Лучшие практики для редких случаев, когда она действительно необходима.
Понимание функции eval()
eval() вычисляет или выполняет строку кода JavaScript. Если строка является выражением, eval() вычисляет его и возвращает результат. Если строка содержит одно или несколько операторов, eval() выполняет их и возвращает значение последнего выражения-оператора (или undefined).
Синтаксис
eval(string)Параметры:
string— строка исходного кода JavaScript для вычисления.
Возвращаемое значение: результат последнего вычисленного выражения или undefined, если строка не содержит выражений.
Если string не является строкой (например, является числом), eval() возвращает аргумент без изменений.
Пример использования
В этом примере вычисляется простое арифметическое выражение и выводится результат:
Практические случаи использования eval()
Несмотря на то что функция eval() может быть очень мощной, её использование зачастую не рекомендуется из-за проблем с безопасностью и производительностью. Однако существуют сценарии, в которых eval() может быть полезна.
Динамическое выполнение кода
В ситуациях, когда код нужно генерировать и выполнять динамически, eval() может быть практичным решением. Например, создание калькулятора, который вычисляет пользовательский ввод как математическое выражение:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dynamic Calculator</title>
</head>
<body>
<p>Add any arithmetic expression like 12 + 5 and hit 'Calculate' button!</p>
<div>
<input type="text" id="expression" placeholder="Enter expression" /> <!-- User input field -->
<button onclick="evaluateExpression()">Calculate</button> <!-- Button to trigger calculation -->
</div>
<div id="calcResult"></div> <!-- Element to display result -->
<script>
function evaluateExpression() {
const expression = document.getElementById('expression').value; // Get user input
try {
const result = eval(expression); // Evaluate the expression
document.getElementById('calcResult').textContent = `Result: ${result}`; // Display the result
} catch (e) {
document.getElementById('calcResult').textContent = 'Invalid expression'; // Handle invalid input
}
}
</script>
</body>
</html>Разбор JSON с помощью eval()
До появления JSON.parse() для разбора JSON-строк использовался eval(). Однако сейчас рекомендуется использовать JSON.parse() из соображений безопасности.
При использовании eval() для разбора JSON-строк крайне важно убедиться, что JSON-строка интерпретируется как выражение-object, а не как блочный оператор. Для этого JSON-строка оборачивается в круглые скобки перед передачей в eval(). Это гарантирует, что JavaScript воспринимает строку как выражение-object, предотвращая синтаксические ошибки, которые могут возникнуть при интерпретации строки как блочного оператора.
Всегда предпочитайте JSON.parse() вместо eval() для разбора JSON. JSON.parse() принимает только данные, поэтому никогда не может выполнить внедрённый скрипт.
Прямой и косвенный eval, и область видимости
То, какие переменные видит eval(), зависит от способа вызова.
Прямой вызов — eval(code), написанный буквально — выполняет код в текущей области видимости. Он может читать и даже создавать переменные там, где вызывается:
Косвенный вызов — вызов eval через любую ссылку, отличную от буквального имени, например (0, eval)(code) или сохранение в переменную — выполняет код в глобальной области видимости. Он не может видеть локальные переменные:
В строгом режиме даже прямой eval() получает собственную область видимости: переменные, объявленные в нём, не «утекают» в окружающую функцию. Это ещё одна причина, по которой современный код редко нуждается в eval().
Проблемы безопасности и производительности
Использование eval() может создавать серьёзные риски безопасности и проблемы с производительностью. Вот почему:
Риски безопасности
eval() может выполнять любой код JavaScript, что делает его уязвимым для инъекционных атак. Злоумышленник потенциально может внедрить вредоносный код, что приведёт к серьёзным нарушениям безопасности.
Пример риска безопасности:
Представьте, что у вас есть веб-приложение, которое принимает пользовательский ввод и вычисляет его с помощью eval(). Без надлежащей валидации злоумышленник может воспользоваться этим для выполнения вредоносного кода.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>eval() Security Risk Example</title>
</head>
<body>
<div>
<p>Hit 'Run Code' button!</p>
<input type="text" id="userInput" placeholder="Enter code" value="alert('Hacked!')" /> <!-- User input field -->
<button onclick="evaluateUserInput()">Run Code</button> <!-- Button to run code -->
</div>
<div id="userInputResult"></div> <!-- Element to display result -->
<script>
function evaluateUserInput() {
const input = document.getElementById('userInput').value; // Get user input
try {
const result = eval(input); // Evaluate the user input
document.getElementById('userInputResult').textContent = `Result: ${result}`; // Display the result
} catch (e) {
document.getElementById('userInputResult').textContent = 'Error in evaluation'; // Handle evaluation error
}
}
</script>
</body>
</html>В этом примере, если пользователь введёт что-то вроде alert('Hacked!'), функция eval() выполнит это, создав нарушение безопасности путём отображения диалогового окна. Злоумышленник может внедрить более опасный код, потенциально скомпрометировав всю систему.
Проблемы с производительностью
eval() выполняет код в той же области видимости, из которой вызывается, что препятствует оптимизациям движка JavaScript. Это может привести к снижению производительности по сравнению с другими подходами.
Пример проблемы с производительностью:
Рассмотрим сценарий, в котором нужно многократно выполнять серию вычислений. Использование eval() для этой задачи может существенно повлиять на производительность.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Performance Issues with eval()</title>
</head>
<body>
<div id="evalResult"></div>
<div id="functionResult"></div>
<script>
// Using eval()
const evalStartTime = performance.now();
for (let i = 0; i < 100000; i++) {
eval("3 * 4 + 5");
}
const evalEndTime = performance.now();
const evalDuration = evalEndTime - evalStartTime;
document.getElementById('evalResult').textContent = `Time taken with eval(): ${evalDuration} ms`;
// Using Function constructor
const func = new Function('return 3 * 4 + 5');
const funcStartTime = performance.now();
for (let i = 0; i < 100000; i++) {
func();
}
const funcEndTime = performance.now();
const funcDuration = funcEndTime - funcStartTime;
document.getElementById('functionResult').textContent = `Time taken with Function constructor: ${funcDuration} ms`;
</script>
</body>
</html>В этом примере сравнивается производительность eval() с конструктором Function. Мы выполняем простую арифметическую операцию (3 * 4 + 5) 100 000 раз обоими методами и измеряем затраченное время.
- Использование
eval(): код внутриeval()вычисляется повторно, что медленнее, поскольку препятствует определённым оптимизациям. - Использование конструктора
Function: конструкторFunctionсоздаёт функцию, выполняющую ту же операцию, но быстрее, поскольку позволяет движкам JavaScript оптимизировать повторное выполнение.
Результаты, отображаемые на веб-странице, показывают время, затраченное каждым методом, демонстрируя, что eval() работает медленнее из-за проблем с производительностью.
Избегайте eval(), если это не абсолютно необходимо. В первую очередь обращайтесь к более безопасным альтернативам: конструктор Function — для динамических функций, JSON.parse() — для данных.
Альтернативы eval()
Для более безопасного и эффективного выполнения кода рассмотрите следующие альтернативы:
Использование конструктора Function
Конструктор Function создаёт новый объект-функцию, аналогично eval(), но с лучшей безопасностью и производительностью.
Использование JSON.parse()
Для разбора JSON-строк предпочтительным методом является JSON.parse().
Лучшие практики использования eval()
Если вам необходимо использовать eval(), придерживайтесь следующих лучших практик для снижения рисков:
1. Валидируйте входные данные
Убедитесь, что входные данные для eval() строго проверяются, чтобы предотвратить внедрение кода. Это означает разрешение только безопасных символов и выражений.
Пример:
В этом примере для проверки пользовательского ввода используется регулярное выражение. Допускаются только числовые символы и основные арифметические операторы. Если ввод соответствует шаблону, он вычисляется с помощью eval(). В противном случае он отклоняется как недопустимый.
2. Используйте Try-Catch
Оборачивайте eval() в блок try-catch для корректной обработки возможных ошибок. Это предотвращает полный сбой приложения, если eval() сталкивается с ошибкой.
Пример:
Здесь синтаксические ошибки в пользовательском вводе обрабатываются с помощью блока try-catch. Если eval() выбрасывает ошибку, она перехватывается, и вместо сбоя приложения выводится сообщение об ошибке.
3. Ограничивайте область видимости
Минимизируйте область видимости переменных, доступных для eval(), чтобы ограничить потенциальный ущерб от вредоносного кода. Используйте немедленно вызываемое функциональное выражение (IIFE) для создания локальной области видимости.
Пример:
В этом примере функция restrictedEval определена внутри IIFE для создания локальной области видимости. Эта функция вычисляет входные данные с помощью eval(), но с локально определёнными переменными a и b. Глобальные переменные a и b недоступны, что демонстрирует ограничение области видимости.
Следуя этим лучшим практикам, вы можете снизить риски, связанные с использованием eval(), сохранив при этом возможности динамического выполнения кода там, где это необходимо.
Заключение
Функция eval() в JavaScript предоставляет мощный инструмент для динамического выполнения кода, однако сопряжена со значительными рисками и недостатками производительности. На практике почти любая задача, которая, казалось бы, требует eval(), имеет более безопасное решение: используйте JSON.parse() для данных, конструктор Function для динамических функций и поиск по object/array вместо построения имён переменных из строк. Если вам всё же необходимо использовать eval(), валидируйте входные данные, оборачивайте вызов в try...catch и делайте его область видимости как можно меньше.
Связанные темы
- Синтаксис "new Function" — более безопасный способ создания функции из строки.
- Работа с JSON —
JSON.parse()иJSON.stringify(). - Обработка ошибок: try...catch — перехват ошибок, выброшенных вычисляемым кодом.
- Строгий режим: "use strict" — как строгий режим меняет область видимости
eval().