renovations
This commit is contained in:
parent
c7d4c7e3ff
commit
e1948130f6
170 changed files with 1496 additions and 1161 deletions
12
1-js/7-js-misc/4-eval/1-eval-calculator/solution.md
Normal file
12
1-js/7-js-misc/4-eval/1-eval-calculator/solution.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
Вычислить любое выражение нам поможет `eval`:
|
||||
|
||||
```js
|
||||
//+ demo run
|
||||
var expr = prompt("Введите выражение?", '2*3+2');
|
||||
|
||||
alert(eval(expr));
|
||||
```
|
||||
|
||||
При этом посетитель потенциально может делать все, что угодно.
|
||||
|
||||
Чтобы ограничить выражения только математикой, вводимую строку нужно проверять при помощи [регулярных выражений](/regular-expressions-javascript) на наличие любых символов, кроме букв, пробелов и знаков пунктуации.
|
9
1-js/7-js-misc/4-eval/1-eval-calculator/task.md
Normal file
9
1-js/7-js-misc/4-eval/1-eval-calculator/task.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Eval-калькулятор
|
||||
|
||||
[importance 4]
|
||||
|
||||
Напишите интерфейс, который принимает математическое выражение (`prompt`) и возвращает его результат.
|
||||
|
||||
Проверять выражение на корректность не требуется.
|
||||
|
||||
[demo /]
|
265
1-js/7-js-misc/4-eval/article.md
Normal file
265
1-js/7-js-misc/4-eval/article.md
Normal file
|
@ -0,0 +1,265 @@
|
|||
# Запуск кода из строки: eval
|
||||
|
||||
Функция `eval(code)` позволяет выполнить код, переданный ей в виде строки.
|
||||
|
||||
Этот код будет выполнен в *текущей области видимости*.
|
||||
[cut]
|
||||
|
||||
## Использование eval
|
||||
|
||||
В простейшем случае `eval` всего лишь выполняет код, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var a = 1;
|
||||
|
||||
(function() {
|
||||
|
||||
var a = 2;
|
||||
|
||||
*!*
|
||||
eval(' alert(a) '); // 2
|
||||
*/!*
|
||||
|
||||
})()
|
||||
```
|
||||
|
||||
Но он может не только выполнить код, но и вернуть результат.
|
||||
|
||||
**Вызов `eval` возвращает последнее вычисленное выражение**:
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( eval('1+1') ); // 2
|
||||
```
|
||||
|
||||
**При вызове `eval` имеет полный доступ к локальным переменным.**
|
||||
|
||||
Это означает, что текущие переменные могут быть изменены или дополнены:
|
||||
|
||||
```js
|
||||
//+ untrusted refresh run
|
||||
var x = 5;
|
||||
eval(" alert(x); x = 10"); // 5, доступ к старому значению
|
||||
alert(x); // 10, значение изменено внутри eval
|
||||
```
|
||||
|
||||
[smart header="В строгом режиме `eval` имеет свою область видимости "]
|
||||
В строгом режиме функционал `eval` чуть-чуть меняется.
|
||||
|
||||
При `use strict` код внутри `eval` по-прежнему сможет читать и менять внешние переменные, однако переменные и функции, объявленные внутри `eval`, не попадут наружу.
|
||||
|
||||
```js
|
||||
//+ untrusted refresh run
|
||||
"use strict";
|
||||
|
||||
*!*
|
||||
eval("var a = 5; function f() { }");
|
||||
*/!*
|
||||
alert(a); // ошибка, переменная не определена
|
||||
// функция f тоже не видна снаружи
|
||||
```
|
||||
|
||||
Иными словами, в новом стандарте `eval` имеет свою область видимости, а к внешним переменным обращается через замыкание, аналогично тому, как работают обычные функции.
|
||||
[/smart]
|
||||
|
||||
## Неграмотное использование eval
|
||||
|
||||
Начнём с того, что `eval` применяется очень редко. Действительно редко. Есть даже такое выражение "eval is evil" (eval -- зло).
|
||||
|
||||
Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас, и некоторые вещи без `eval` было сделать невозможно. Но те времена давно прошли. И теперь найти тот случай, когда действительно надо выполнить код из строки -- это надо постараться.
|
||||
|
||||
Но если вы действительно знаете, что это именно тот случай и вам необходим `eval` -- есть ряд вещей, которые нужно иметь в виду.
|
||||
|
||||
Доступ к локальным переменным -- худшее, что можно сделать при `eval`.
|
||||
|
||||
Дело в том, что локальные переменные могут быть легко переименованы:
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
var phrase = "Привет";
|
||||
eval(str);
|
||||
}
|
||||
```
|
||||
|
||||
Переменная `phrase` может быть переименована в `hello`, и если строка `str` обращается к ней -- будет ошибка.
|
||||
|
||||
Современные средства сжатия JavaScript переименовывают локальные переменные автоматически. Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять `phrase` на `p`, то никто этого не заметит.
|
||||
|
||||
До сжатия:
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
var phrase = "Привет";
|
||||
alert(phrase);
|
||||
}
|
||||
```
|
||||
|
||||
После сжатия:
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
var a = "Привет";
|
||||
alert(a);
|
||||
}
|
||||
```
|
||||
|
||||
На самом деле всё ещё проще -- в данном случае утилита сжатия автоматически уберёт переменную `a` и код станет таким:
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
alert("Привет");
|
||||
}
|
||||
```
|
||||
|
||||
Итак, если где-то в функции есть `eval`, то его взаимодействие с локальными переменными будет нарушено с непредсказуемыми побочными эффектами.
|
||||
|
||||
Некоторые инструменты сжатия предупреждают, когда видят `eval` или стараются вообще не сжимать такой код вместе с его внешними функциями, но всё это борьба с последствиями кривого кода.
|
||||
|
||||
Как правило, `eval` не нужен, именно поэтому говорят, "eval is evil".
|
||||
|
||||
## Запуск скрипта в глобальной области
|
||||
|
||||
Ок, взаимодействовать с локальными переменными нельзя.
|
||||
|
||||
Но допустим мы загрузили с сервера или вручную сгенерировали скрипт, который нужно выполнить. Желательно, в глобальной области, вне любых функций, чтобы он уж точно к локальным переменным отношения не имел.
|
||||
|
||||
Здесь `eval` может пригодиться. Есть два трюка для выполнения кода в глобальной области:
|
||||
|
||||
<ol>
|
||||
<li>Везде, кроме IE8-, достаточно вызвать `eval` не напрямую, а через `window.eval`.
|
||||
|
||||
Вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var a = 1;
|
||||
|
||||
(function() {
|
||||
|
||||
var a = 2;
|
||||
*!*
|
||||
window.eval(' alert(a) '); // 1, выполнено глобально везде, кроме IE8-
|
||||
*/!*
|
||||
})();
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>В IE8- можно применить нестандартную фунцию [execScript](http://msdn.microsoft.com/en-us/library/ie/ms536420%28v=vs.85%29.aspx). Она, как и `eval`, выполняет код, но всегда в глобальной области видимости и не возвращает значение.</li>
|
||||
</ol>
|
||||
|
||||
Оба способа можно объединить в единой функции `globalEval(code)`, выполняющей код без доступа к локальным переменным:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
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`, которая позволяет корректно взаимодействовать c внешним кодом: `new Function`.**
|
||||
|
||||
Вызов `new Function('a,b', '..тело..')` создает функцию с указанными аргументами `a,b` и телом. Как мы помним, доступа к текущему замыканию у такой функции не будет, но можно передать параметры и получить результат.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
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`.
|
||||
|
||||
Вызов `eval(code)` выполняет код и, если это выражение, то возвращает его значение, поэтому можно в качестве кода передать JSON.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str= '{ \
|
||||
"name": "Вася", \
|
||||
"age": 25 \
|
||||
}';
|
||||
|
||||
*!*
|
||||
var user = eval('(' + str + ')');
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Вася
|
||||
```
|
||||
|
||||
Зачем здесь нужны скобки `eval( '(' + str + ')' )`, почему не просто `eval(str)`?
|
||||
|
||||
...Всё дело в том, что в JavaScript с фигурной скобки `{` начинаются не только объекты, а в том числе и "блоки кода". Что имеется в виду в данном случае -- интерпретатор определяет по контексту. Если в основном потоке кода -- то блок, если в контексте выражения, то объект.
|
||||
|
||||
Поэтому если передать в `eval` объект напрямую, то интерпретатор подумает, что это на самом деле блок кода, а там внутри какие-то двоеточия...
|
||||
|
||||
Вот, для примера, `eval` без скобок, он выдаст ошибку:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = eval( '{ "name": "Вася", "age": 25 }' );
|
||||
```
|
||||
|
||||
А если `eval` получает выражение в скобках `( ... )`, то интерпретатор точно знает, что это не блок кода, а объект:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = eval( '( { "name": "Вася", "age": 25 } )' );
|
||||
alert(user.age); // 25
|
||||
```
|
||||
|
||||
[warn header="Осторожно, злой JSON!"]
|
||||
Если мы получаем JSON из недоверенного источника, например с чужого сервера, то разбор через `eval` может быть опасен.
|
||||
|
||||
Например, чужой сервер может быть взломан (за свой-то код мы отвечаем, а за чужой -- нет) и вместо JSON вставлен злонамеренный JavaScript-код.
|
||||
|
||||
**Поэтому рекомендуется, всё же, использовать `JSON.parse`.**
|
||||
|
||||
При разборе через `JSON.parse` некорректный JSON просто приведёт к ошибке, а вот при разборе через `eval` этот код реально выполнится, он может вывести что-то на странице, перенаправить посетителя куда-то и т.п.
|
||||
|
||||
Если вам важна поддержка IE7, в котором `JSON.parse нет`, то от злого кода можно защититься проверкой текста при помощи регулярного выражения из [стандарта RFC 4627, секция 6](https://www.ietf.org/rfc/rfc4627.txt) или подключить библиотеку [json2](https://github.com/douglascrockford/JSON-js/blob/master/json2.js).
|
||||
[/warn]
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Функция `eval(str)` выполняет код и возвращает последнее вычисленное выражение. В современном JavaScript она используется редко.</li>
|
||||
<li>Вызов `eval` может читать и менять локальные переменные. Это -- зло, которого нужно избегать.</li>
|
||||
<li>Для выполнения скрипта в глобальной области используются трюк с `window.eval/execScript`. При этом локальные переменные не будут затронуты, так что такое выполнение безопасно и иногда, в редких архитектурах, может быть полезным.</li>
|
||||
<li>Если нужно выполняемый код всё же должен взаимодействовать с локальными переменными -- используйте `new Function`. Создавайте функцию из строки и передавайте переменные ей, это надёжно и безопасно.</li>
|
||||
</ul>
|
||||
|
||||
Ещё примеры использования `eval` вы найдёте далее, в главе [](/json).
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue