簡體   English   中英

為JavaScript中的eval()指定scope?

[英]Specify scope for eval() in JavaScript?

有什么辦法可以在特定的 scope (但不是全局)上執行 eval() 嗎?

例如,下面的代碼不起作用(a 在第二個語句中未定義),因為它們在不同的 scope 上:

eval(var a = 1); 
eval(alert(a));

如果可能的話,我想即時創建一個 scope。 例如(語法肯定是錯誤的,但只是為了說明這個想法)

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
}

關於如何實現這樣的目標有什么想法嗎? 謝謝!

您可以使用“使用嚴格”在 eval 本身中包含 eval'ed 代碼。

其次,嚴格模式代碼的eval不會將新變量引入周圍的范圍 在普通代碼中eval("var x;")將變量x引入到周圍的函數或全局作用域中。 這意味着,通常,在包含對eval的調用的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);
}

創建您希望存在於作用域中的變量作為函數中的局部變量。 然后,從該函數返回一個本地定義的函數,該函數具有單個參數並對其調用eval 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/

簡單如餡餅。

 // Courtesy of Hypersoft-Systems: U.-S.-A. function scopeEval(scope, script) { return Function('"use strict";return (' + script + ')').bind(scope)(); } scopeEval(document, 'alert(this)');

這是一個 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>

這對我最有效:

const scopedEval = (scope, script) => Function(`"use strict"; ${script}`).bind(scope)();

用法:

scopedEval({a:1,b:2},"return this.a+this.b")

您可以查看vm-browserify項目,該項目將與browserify結合使用。

它的工作原理是創建<iframe> s,並在該<iframe> eval代碼。 代碼實際上非常簡單,因此如果您不想使用庫本身,您可以根據自己的目的調整基本思想。

窮人的方法:

如果您的范圍不是太動態,只需幾個靜態和只讀聲明,只需將它放在一個字符串中並與您想要執行的字符串連接,如下所示:

 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 )。

這是我發現的最簡單的方法,但它不使用 eval。

function execInContext(code, context)
{   
    return Function(...Object.keys(context), 'return '+ code (...Object.values(context));
}

這里我們創建了一個Function對象。 Function構造函數接受一個參數數組,最后一個是函數要執行的代碼,所有其他都是函數的參數名稱。 我們正在做的是創建一個函數,該函數具有與context對象中的字段同名的參數,然后使用context字段的值調用此函數。 所以如果你打電話

execInContext('myVar', {myVar: 'hi!'});

它和

((myVar) => { return myVar; })('hi!');

結果是hi!

簡單

const evaluate = (context, expr) =>
        Function(Object.keys(context).join(','), `return ${expr}`)
        (...Object.values(context));

用法

const result = evaluate({a: 1, b: 2, c: {d: 3}}, "a+b+c.d"); // 6

我不確定這增加了多少,但我想我會發布我的版本的Function基於構造函數的解決方案和現代語法糖,我認為這是一個很好的解決方案,可以避免用包含評估文本的內部版本污染 scope . (通過犧牲this上下文,即使在use strict;代碼中也可以delete其屬性)

class ScopedEval {
    /** @param {Record<string,unknown>} scope */
    constructor(scope) {
        this.scope = scope;
    }
    eval(__script) {
        return new Function(...Object.keys(this.scope),`
                return eval(
                    '"use strict";delete this.__script;' 
                    + this.__script
                );
            `.replace(/[\n\t]|  +/g,'')
        ).bind({__script})(...Object.values(this.scope));
    }
}

就我個人而言,我更喜歡在添加或調整 scope 以及評估某些代碼時能夠分開,因此可以像這樣使用:

 const context = { hi: 12, x: () => 'this is x', get xGet() { return 'this is the xGet getter' } }; const x = new ScopedEval(context) console.log(x.eval('"hi = " + hi')); console.log(x.eval(` let k = x(); "x() returns " + k `)); console.log(x.eval('xGet')); x.scope.someId = 42; console.log(x.eval('(() => someId)()'))

當心; 似乎有些實例仍然可以訪問外部變量。 下面的代碼應該會失敗,但是如果你運行它,你會看到它有效

 let exp = 'a+b'; let a = 1; let b = 2; function scopeEval(scope, script) { return Function('"use strict";return (' + script + ')').bind(scope)(); } console.log(scopeEval({}, exp));

提供腳本范圍、應用程序級范圍和應用程序級腳本此對象的腳本評估。 它是安全的,不會泄漏到應用程序

它還提供了一個控制台代理來重定向控制台 output,它在腳本范圍內有效,因此腳本調用console.log會將 go 發送到代理。

 // app level scope, to share additional function and variable that can be used without `this` keyword. const scope = {}; // app level this obj for all scripts, use this to reference it; // so we could share data between scripts. const thisObj = {}; /** * execute scripts * @param thisObj 'this' is an app level obj to share data between scripts * @param scriptsScope app level scope to share some global predefined functions. * @param localScope we can setup a 'console' proxy is in this scope. * @param script code to execute. */ const scopedEval = function scopedEval(thisObj, scriptsScope, localScope, script) { const context = {...scriptsScope, ...localScope }; // create new Function with keys from context as parameters, 'script' is the last parameter. const evaluator = Function.apply(null, [...Object.keys(context), 'script', `"use strict"; try{${script}} catch (e) { console.error(e); }`]); // call the function with values from context and 'script' as arguments. evaluator.apply(thisObj, [...Object.values(context), script]); } /** * create a proxy for console. that will also write to the consoleElement * @param consoleElement the ui element to show the console logs, ie a div. * @returns {Proxy} */ const consoleProxy = consoleElement => new Proxy(console, { get: function (target, propKey) { const originalMethod = target[propKey]; return function (...args) { // get time with milliseconds const now = new Date(); const time = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0') + ':' + now.getSeconds().toString().padStart(2,'0') + '.' + now.getMilliseconds().toString().padStart(3,'0'); // text to show const text = document.createTextNode(`${time}: ${args.join(' ')}\n`); const span = document.createElement('span'); span.appendChild(text); if (propKey === 'error') { span.style.color = 'red'; } else if (propKey === 'warn') { span.style.color = 'orange'; } else if (propKey === 'info') { span.style.color = 'blue'; } else if (propKey === 'debug') { span.style.color = 'gray'; } consoleElement.appendChild(span); // original console logs, if you need //originalMethod.apply(target, args); } } }); const codes = document.querySelector('.codes'); const script1 = codes.querySelector('.script1').textContent; const script2 = codes.querySelector('.script2').textContent; const scriptConsole1 = codes.querySelector('.script-console1'); const scriptConsole2 = codes.querySelector('.script-console2'); scopedEval(thisObj, scope, { console: consoleProxy(scriptConsole1) }, script1); scopedEval(thisObj, scope, { console: consoleProxy(scriptConsole2) }, script2);
 .code { background: lightgray; }
 <div class="codes"> <div> <pre class="code"> <code class="script1"> console.log('hello'); this.a = 1 // error b++ </code> </pre> <pre class="script-console1"> </pre> <div> <div> <pre class="code"> <code class="script2"> this.a++ console.log(this.a); </code> </pre> <pre class="script-console2"> </pre> </div> <div>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM