Укажите область для eval() в JavaScript?
есть ли способ, которым я могу выполнить eval() в определенной области (но НЕ глобально)?
например, следующий код не работает (a undefined во втором выражении), поскольку они находятся в разных областях:
eval(var a = 1);
eval(alert(a));
Если возможно, я хотел бы создать область действия "на лету". например (синтаксис определенно неверен, но просто для иллюстрации идеи)
var scope1;
var scope2;
with scope1{
eval(var a = 1); eval(alert(a)); // this will alert 1
}
with scope2{
eval(var a = 1); eval(a++); eval(alert(a)); // this will alert 2
}
with scope1{
eval(a += 2); eval(alert(a)); // this will alert 3 because a is already defined in scope1
}
Любая идея о том, как добиться чего-то подобного? Спасибо!
Ответы
Ответ 1
вы можете использовать "use strict" , чтобы содержать код eval'ed внутри самого eval.
Во-вторых, eval
строкового кода режима не вводит новые переменные в окружающий объем. В нормальном коде eval("var x;")
вводится переменная x
в окружающую функцию или глобальную область. Это означает, что, вообще говоря, в функции, содержащей вызов eval
, каждое имя, не относящееся к аргументу или локальной переменной, должно быть сопоставлено определенному определению во время выполнения (поскольку этот eval
мог бы ввести новую переменную, которая скрыть внешнюю переменную). В строгом режиме eval
создает переменные только для оцениваемого кода, поэтому eval не может повлиять на то, ссылается ли имя на внешнюю переменную или на некоторую локальную переменную
var x = 17; //a local variable
var evalX = eval("'use strict'; var x = 42; x"); //eval an x internally
assert(x === 17); //x is still 17 here
assert(evalX === 42); //evalX takes 42 from eval'ed x
Если функция объявлена с использованием "use strict", все в ней будет выполняться в строгом режиме. следующее будет сделано так же, как указано выше:
function foo(){
"use strict";
var x = 17;
var evalX = eval("var x = 42; x");
assert(x === 17);
assert(evalX === 42);
}
Ответ 2
Создайте переменные, которые вы хотите использовать в своей области, в качестве локальных переменных в функции. Затем из этой функции возвращаем локально определенную функцию, которая имеет один аргумент и вызывает на ней eval
. Этот экземпляр eval
будет использовать область своей содержащей функции, которая вложена в область вашей функции верхнего уровня. Каждый вызов функции верхнего уровня создает новую область с новым экземпляром функции eval. Чтобы сохранить все динамическое, вы даже можете использовать вызов eval
в функции верхнего уровня, чтобы объявить переменные, которые будут локальными для этой области.
Пример кода:
function makeEvalContext (declarations)
{
eval(declarations);
return function (str) { eval(str); }
}
eval1 = makeEvalContext ("var x;");
eval2 = makeEvalContext ("var x;");
eval1("x = 'first context';");
eval2("x = 'second context';");
eval1("window.alert(x);");
eval2("window.alert(x);");
https://jsfiddle.net/zgs73ret/
Ответ 3
Вы можете посмотреть проект vm-browserify, который будет использоваться совместно с browserify.
Он работает, создавая <iframe>
s и eval
код в этом <iframe>
. Код на самом деле довольно прост, поэтому вы можете адаптировать базовую идею для своих целей, если вы не хотите использовать самую библиотеку.
Ответ 4
Вот 20-строчный или такой JS-класс, который реализует расширяемый контекст, используя eval в лексической области:
// Scope class
// aScope.eval(str) -- eval a string within the scope
// aScope.newNames(name...) - adds vars to the scope
function Scope() {
"use strict";
this.names = [];
this.eval = function(s) {
return eval(s);
};
}
Scope.prototype.newNames = function() {
"use strict";
var names = [].slice.call(arguments);
var newNames = names.filter((x)=> !this.names.includes(x));
if (newNames.length) {
var i, len;
var totalNames = newNames.concat(this.names);
var code = "(function() {\n";
for (i = 0, len = newNames.length; i < len; i++) {
code += 'var ' + newNames[i] + ' = null;\n';
}
code += 'return function(str) {return eval(str)};\n})()';
this.eval = this.eval(code);
this.names = totalNames;
}
}
// LOGGING FOR EXAMPLE RUN
function log(s, eval, expr) {
s = '<span class="remark">' + String(s);
if (expr) {
s += ':\n<b>' + expr + '</b> --> ';
}
s += '</span>';
if (expr) {
try {
s += '<span class="result">' + JSON.stringify(eval(expr)) + '</span>';
} catch (err) {
s += '<span class="error">' + err.message + '</span>';
}
}
document.body.innerHTML += s + '\n\n';
}
document.body.innerHTML = '';
// EXAMPLE RUN
var scope = new Scope();
log("Evaluating a var statement doesn't change the scope but newNames does (should return undefined)", scope.eval, 'var x = 4')
log("X in the scope object should raise 'x not defined' error", scope.eval, 'x');
log("X in the global scope should raise 'x not defined' error", eval, 'x');
log("Adding X and Y to the scope object");
scope.newNames('x', 'y');
log("Assigning x and y", scope.eval, 'x = 3; y = 4');
log("X in the global scope should still raise 'x not defined' error", eval, 'x');
log("X + Y in the scope object should be 7", scope.eval, 'x + y');
log("X + Y in the global scope should raise 'x not defined' error", eval, 'x + y');
.remark {
font-style: italic;
}
.result, .error {
font-weight: bold;
}
.error {
color: red;
}
<body style='white-space: pre'></body>