Будьте в курсе последних событий, подпишитесь на обновления сайта

Запуск кода из строки: метод eval

Запуск кода из строки: метод eval

Здравствуйте! В этом уроке речь пойдет о таком может малоизвестном методе eval. Функция eval(code) позволяет выполнить  любой код, переданный ей в виде строки.  И в  этом кроется ее опасность  ведь если ей передать зловредный код в виде строки, то он будет выполнен.

Этот код будет выполнен в текущей области видимости.

Метод eval выполнение кода JS

Использование eval

В простейшем случае eval всего лишь выполняет код, вот пример:

var a = 1; (function() { var a = 2; eval(' alert(a) '); // 2 })()

Но он может не только выполнить код, но и также вернуть результат.
Вызов eval возвращает последнее вычисленное выражение, например:

alert( eval('2+1') ); // 3

При вызове 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 – есть несколько нбансов, которые нужно иметь в виду.

Читайте также  Переопределение функций в JavaScript

Доступ к локальным переменным – худшее, что можно сделать при 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 может пригодиться. Имеются 2 трюка для выполнения кода в глобальной области:

  1. Везде, кроме IE8-, достаточно вызвать eval не напрямую, а через window.eval.Вот так:
    var a = 1; 
    (function() { 
    var a = 2; 
    window.eval(' alert(a) '); // 1, выполнено глобально везде, кроме IE8- })();
    
  1. В 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 объект напрямую, то интерпретатор подумает, что это на самом деле блок кода, а там внутри какие-то двоеточия

Читайте также  Выделение на странице: Range, TextRange и Selection

Вот, для примера, eval без скобок, он выдаст ошибку:

var user = eval('{  "name": "Вася",  "age": 25  }');

А если eval получает выражение в скобках ( … ), то интерпретатор точно знает, что это не блок кода, а объект:

var user = eval('( {  "name": "Вася",  "age": 25  } )'); alert( user.age ); // 25

Осторожно, злой JSON!

Если мы получаем JSON из недоверенного источника, например с чужого сервера, то разбор через eval может быть и опасен.

Например, чужой сервер может быть взломан (за свой-то код мы отвечаем, а вот за чужой – нет), и вместо JSON вставлен злонамеренный JavaScript-код.

Поэтому рекомендуется, всё же, использовать JSON.parse.

При разборе через JSON.parse некорректный JSON просто приведёт к ошибке, а вот при разборе через eval этот код реально выполнится, и он может вывести что-то на странице, перенаправить посетителя куда-то и т.п да мало ли что еще можно придумать.

Итоги

  • Функция eval(str) выполняет код и возвращает последнее вычисленное выражение. В современном JavaScript она используется очень редко.
  • Вызов eval может читать и менять локальные переменные. Это – зло, поэтому следует этого избегать.
  • Для выполнения скрипта в глобальной области используются трюк с window.eval/execScript. При этом локальные переменные не будут затронуты, так что такое выполнение безопасно и иногда, в редких архитектурах, может быть полезным.
  • Если выполняемый код всё же должен взаимодействовать с локальными переменными – используйте выражение new Function. Создавайте функцию из строки и передавайте переменные ей, это надёжно и безопасно.

 

Задания

Eval-калькулятор

Вам надо написать  интерфейс, который принимает математическое выражение (prompt) и возвращает его результат.

Проверять выражение на корректность не требуется.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: