Функция eval(code)
позволяет выполнить код, переданный ей в виде строки.
Этот код будет выполнен в текущей области видимости.
Использование eval
В простейшем случае eval
всего лишь выполняет код, например:
var a = 1;
(function() {
var a = 2;
eval(' alert(a) '); // 2
})()
Но он может не только выполнить код, но и вернуть результат.
Вызов eval
возвращает последнее вычисленное выражение, например:
alert( eval('1+1') ); // 2
При вызове eval
имеет полный доступ к локальным переменным.
Это означает, что текущие переменные могут быть изменены или дополнены:
var x = 5;
eval(" alert( x ); x = 10"); // 5, доступ к старому значению
alert( x ); // 10, значение изменено внутри eval
eval
имеет свою область видимости В строгом режиме функционал eval
чуть-чуть меняется.
При use strict
код внутри eval
по-прежнему сможет читать и менять внешние переменные, однако переменные и функции, объявленные внутри eval
, не попадут наружу.
"use strict";
eval("var a = 5; function f() { }");
alert( a ); // ошибка, переменная не определена
// функция f тоже не видна снаружи
Иными словами, в новом стандарте eval
имеет свою область видимости, а к внешним переменным обращается через замыкание, аналогично тому, как работают обычные функции.
Неграмотное использование eval
Начнём с того, что eval
применяется очень редко. Действительно редко. Есть даже такое выражение «eval is evil» (eval – зло).
Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас, и некоторые вещи без eval
было сделать невозможно. Но те времена давно прошли. И теперь найти тот случай, когда действительно надо выполнить код из строки – это надо постараться.
Но если вы действительно знаете, что это именно тот случай и вам необходим eval
– есть ряд вещей, которые нужно иметь в виду.
Доступ к локальным переменным – худшее, что можно сделать при eval
.
Дело в том, что локальные переменные могут быть легко переименованы:
function sayHi() {
var phrase = "Привет";
eval(str);
}
Переменная phrase
может быть переименована в hello
, и если строка str
обращается к ней – будет ошибка.
Современные средства сжатия JavaScript переименовывают локальные переменные автоматически. Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять phrase
на p
, то никто этого не заметит.
До сжатия:
function sayHi() {
var phrase = "Привет";
alert( phrase );
}
После сжатия:
function sayHi() {
var a = "Привет";
alert( a );
}
На самом деле всё ещё проще – в данном случае утилита сжатия автоматически уберёт переменную a
и код станет таким:
function sayHi() {
alert( "Привет" );
}
Итак, если где-то в функции есть eval
, то его взаимодействие с локальными переменными будет нарушено с непредсказуемыми побочными эффектами.
Некоторые инструменты сжатия предупреждают, когда видят eval
или стараются вообще не сжимать такой код вместе с его внешними функциями, но всё это борьба с последствиями кривого кода.
Как правило, eval
не нужен, именно поэтому говорят: «eval is evil».
Запуск скрипта в глобальной области
Ок, взаимодействовать с локальными переменными нельзя.
Но, допустим, мы загрузили с сервера или вручную сгенерировали скрипт, который нужно выполнить. Желательно в глобальной области, вне любых функций, чтобы он уж точно к локальным переменным отношения не имел.
Здесь eval
может пригодиться. Есть два трюка для выполнения кода в глобальной области:
-
Везде, кроме IE8-, достаточно вызвать
eval
не напрямую, а черезwindow.eval
.Вот так:
var a = 1; (function() { var a = 2; window.eval(' alert(a) '); // 1, выполнено глобально везде, кроме IE8- })();
-
В IE8- можно применить нестандартную фунцию execScript. Она, как и
eval
, выполняет код, но всегда в глобальной области видимости и не возвращает значение.
Оба способа можно объединить в единой функции globalEval(code)
, выполняющей код без доступа к локальным переменным:
function globalEval(code) { // объединим два способа в одну функцию
window.execScript ? execScript(code) : window.eval(code);
}
var a = 1;
(function() {
var a = 2;
globalEval(' alert(a) '); // 1, во всех браузерах
})();
Внешние данные через new Function
Итак, у нас есть код, который всё же нужно выполнить динамически, через eval
, но не просто скрипт – а ему нужно передать какие-то значения.
Как мы говорили ранее, считать их из локальных переменных нельзя: это подвержено ошибкам при переименовании переменных и сразу ломается при сжатии JavaScript. Да и вообще, неочевидно и криво.
К счастью, существует отличная альтернатива eval
, которая позволяет корректно взаимодействовать с внешним кодом: new Function
.
Вызов new Function('a,b', '..тело..')
создает функцию с указанными аргументами a,b
и телом. Как мы помним, доступа к текущему замыканию у такой функции не будет, но можно передать параметры и получить результат.
Например:
var a = 2,
b = 3;
// вместо обращения к a,b через eval
// будем принимать их как аргументы динамически созданной функции
var mul = new Function('a, b', ' return a * b;');
alert( mul(a, b) ); // 6
JSON и eval
В браузерах IE7- не было методов JSON.stringify
и JSON.parse
, поэтому работа с JSON происходила через eval
.
Этот способ работы с JSON давно устарел, но его можно встретить кое-где в старом коде, так что для примера рассмотрим его.
Вызов eval(code)
выполняет код и, если это выражение, то возвращает его значение, поэтому можно в качестве кода передать JSON.
Например:
var str = '{ \
"name": "Вася", \
"age": 25 \
}';
var user = eval('(' + str + ')');
alert( user.name ); // Вася
Зачем здесь нужны скобки eval( '(' + str + ')' )
, почему не просто eval(str)
?
…Всё дело в том, что в JavaScript с фигурной скобки {
начинаются не только объекты, а в том числе и «блоки кода». Что имеется в виду в данном случае – интерпретатор определяет по контексту. Если в основном потоке кода – то блок, если в контексте выражения, то объект.
Поэтому если передать в eval
объект напрямую, то интерпретатор подумает, что это на самом деле блок кода, а там внутри какие-то двоеточия…
Вот, для примера, eval
без скобок, он выдаст ошибку:
var user = eval('{ "name": "Вася", "age": 25 }');
А если eval
получает выражение в скобках ( ... )
, то интерпретатор точно знает, что это не блок кода, а объект:
var user = eval('( { "name": "Вася", "age": 25 } )');
alert( user.age ); // 25
Если мы получаем JSON из недоверенного источника, например с чужого сервера, то разбор через eval
может быть опасен.
Например, чужой сервер может быть взломан (за свой-то код мы отвечаем, а за чужой – нет), и вместо JSON вставлен злонамеренный JavaScript-код.
Поэтому рекомендуется, всё же, использовать JSON.parse
.
При разборе через JSON.parse
некорректный JSON просто приведёт к ошибке, а вот при разборе через eval
этот код реально выполнится, он может вывести что-то на странице, перенаправить посетителя куда-то и т.п.
Итого
- Функция
eval(str)
выполняет код и возвращает последнее вычисленное выражение. В современном JavaScript она используется редко. - Вызов
eval
может читать и менять локальные переменные. Это – зло, которого нужно избегать. - Для выполнения скрипта в глобальной области используются трюк с
window.eval/execScript
. При этом локальные переменные не будут затронуты, так что такое выполнение безопасно и иногда, в редких архитектурах, может быть полезным. - Если выполняемый код всё же должен взаимодействовать с локальными переменными – используйте
new Function
. Создавайте функцию из строки и передавайте переменные ей, это надёжно и безопасно.
Ещё примеры использования eval
вы найдёте далее, в главе Формат JSON, метод toJSON.
Комментарии
<code>
, для нескольких строк кода — тег<pre>
, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)