特定のスコープでeval()を実行する方法はありますか(ただしグローバルではありません)?
たとえば、次のコードはスコープが異なるため、機能しません(aは2番目のステートメントで定義されていません)。
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
}
このようなことを達成する方法について何かアイデアはありますか?ありがとう!
"use strict" を使用して、評価されたコードをeval自体に含めることができます。
次に、strictモードコードの
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
関数が「usestrict」で宣言されている場合、その関数内のすべてがstrictモードで実行されます。以下は上記と同じことをします:
function foo(){
"use strict";
var x = 17;
var evalX = eval("var x = 42; x");
assert(x === 17);
assert(evalX === 42);
}
スコープに存在させたい変数を関数のローカル変数として作成します。次に、その関数から、単一の引数を持ち、その上で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);");
パイのようにシンプル。
// Courtesy of Hypersoft-Systems: U.-S.-A.
function scopeEval(scope, script) {
return Function('"use strict";return (' + script + ')').bind(scope)();
}
scopeEval(document, 'alert(this)');
vm-browserify プロジェクトを調べることができます。これは、 browserify と組み合わせて使用されます。
これは、<iframe>
sを作成し、その<iframe>
にコードをeval
することで機能します。コードは実際には非常に単純なので、ライブラリ自体を使用したくない場合は、基本的な考え方を自分の目的に適合させることができます。
これは、字句スコープでevalを使用して拡張可能なコンテキストを実装する20行程度のJSクラスです。
// 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>
これは私にとって最もうまくいきました:
const scopedEval = (scope, script) => Function(`"use strict"; ${script}`).bind(scope)();
使用法:
scopedEval({a:1,b:2},"return this.a+this.b")
貧乏人の方法:
スコープがあまり動的ではない場合は、静的で読み取り専用の宣言を2つだけ使用し、それを文字列に入れて、次のように実行する文字列と連結します。
const scopeAll = `
const myFunc = (a, b) => a + b + s;
`
const scope1 = `
${scopeAll}
const s = 'c';
`
const scope2 = `
${scopeAll}
const s = 'd';
`
const myStringToExecute = `
myFunc('a', 'b')
`
console.log(eval(scope1 + myStringToExecute));
console.log(eval(scope2 + myStringToExecute));
ここでのアプローチは、コンテキストオブジェクトが式の評価をパラメータ化できるようにすることでした。
最初に、 Function()コンストラクター を使用して関数が作成されます。このコンストラクターは、コンテキストのすべてのキーと評価する式を受け入れます。本文は評価された式を返します。次に、その関数が、評価するコンテキストと式のすべての値を使用して呼び出されます。
function scopedEval(context, expr) {
const evaluator = Function.apply(null, [...Object.keys(context), 'expr', "return eval(expr)"]);
return evaluator.apply(null, [...Object.values(context), expr]);
}
// Usage
const context = {a: 1, b: 2, c: {d: 3}};
scopedEval(context, "a+b+c.d"); // 6
Function.prototype.apply を使用することにより、引数の数と名前を事前に知る必要はありません。引数のスコープはevaluator
関数であるため、(this.a
を要求する代わりに)式から直接アクセスできます。