如何在严格模式下使用上下文评估自定义 javascript 表达式?
How to evaluate custom javascript expression with the context in strict mode?
更新
我想出了一个简洁的解决方案来解决这个问题,它的行为类似于 node 的 vm 模块。
var VM = function(o) {
eval((function() {
var src = '';
for (var prop in o) {
if (o.hasOwnProperty(prop)) {
src += 'var ' + prop + '=o[\'' + prop + '\'];';
}
}
return src;
})());
return function() {
return eval(arguments[0]);
}
}
然后可以这样使用:
var vm = new VM({ prop1: { prop2: 3 } });
console.assert(3 === vm('prop1.prop2'), 'Property access');
此解决方案仅使用标识符 arguments
覆盖命名空间。
感谢 Ryan Wheale 的想法。
短版
使用 javascript 对象作为上下文评估自定义 javascript 表达式的最佳方法是什么?
var context = { prop1: { prop2: 3 } }
console.assert(3 === evaluate('prop1.prop2', context), 'Simple expression')
console.assert(3 === evaluate('(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()', context), 'Complex expression')
它应该 运行 在最新版本的节点 (0.12) 和撰写本文时 (3/6/2015) 的所有常绿浏览器上。
注意:大多数模板引擎都支持此功能。例如,Jade.
长版
我目前正在开发一个应用程序引擎,它的一个特点是它接受一段代码并使用提供的对象对其进行评估,然后 return 得出结果。
例如,engine.evaluate('prop1.prop2', {prop1: {prop2: 3}})
应该 return 3
.
这可以通过以下方式轻松完成:
function(code, obj) {
with (obj) {
return eval(code);
}
};
但是,with
的用法已知是一种不好的做法,在 ES5 严格模式下不会 运行。
在查看 with
之前,我已经编写了一个替代解决方案:
function(code, obj) {
return (function() {
return eval(code);
}).call(obj, code);
}
但是,此方法需要使用this
。
如:engine.evaluate('this.prop1.prop2', {prop1: {prop2: 3}})
最终用户不应使用任何 "prefix"。
引擎还必须能够评估像
这样的字符串
'prop1.prop2 + 5'
和
'(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()'
以及包含从提供的对象调用函数的那些。
因此,它不能依赖于将 code
字符串单独拆分为 属性 个名称。
这个问题的最佳解决方案是什么?
更新 3:一旦我们弄清楚您真正要问的是什么,问题就很明确了:您不会那样做。特别是在严格模式下。
作为您方法的可行替代方案,请参阅有关 require.js、common.js 和其他允许您在浏览器中加载模块的库的文档。基本上,主要区别在于您不执行 prop1.prop2
而是执行 context.prop1.prop2
。
如果可以使用 context.prop1.prop2
,请参阅 jsfiddle:http://jsfiddle.net/vittore/5rse4jto/
"use strict";
var obj = { prop1 : { prop2: 'a' } }
function evaluate(code, context) {
var f = new Function('ctx', 'return ' + code);
return f(context)
}
alert(evaluate('ctx.prop1.prop2', obj))
alert(evaluate(
'(function() {' +
' console.log(ctx.prop1.prop2);' +
' return ctx.prop1.prop2;' +
'}) ()', obj))
更新:关于如何使用 prop1.prop2
访问属性的原始问题的答案
首先,您可以使用字典符号访问您的变量,即:
obj['prop1']['prop2'] === obj.prop1.prop2
给我几分钟时间想出如何递归执行的示例
已更新:这应该有效(这里是 gist):
function jpath_(o, props) {
if (props.length == 1)
return o[props[0]];
return jpath_(o[props.shift()], props)
}
function jpath(o, path) {
return jpath_(o, path.split('.'))
}
console.log(jpath(obj, 'prop1.prop2'))
我不知道你的所有场景,但这应该让你有一个良好的开端:
http://jsfiddle.net/ryanwheale/e8aaa8ny/
var engine = {
evaluate: function(strInput, obj) {
var fnBody = '';
for(var prop in obj) {
fnBody += "var " + prop + "=" + JSON.stringify(obj[prop]) + ";";
}
return (new Function(fnBody + 'return ' + strInput))();
}
};
更新 - 我感到无聊:http://jsfiddle.net/ryanwheale/e8aaa8ny/3/
var engine = {
toSourceString: function(obj, recursion) {
var strout = "";
recursion = recursion || 0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
strout += recursion ? " " + prop + ": " : "var " + prop + " = ";
switch (typeof obj[prop]) {
case "string":
case "number":
case "boolean":
case "undefined":
strout += JSON.stringify(obj[prop]);
break;
case "function":
// won't work in older browsers
strout += obj[prop].toString();
break;
case "object":
if (!obj[prop])
strout += JSON.stringify(obj[prop]);
else if (obj[prop] instanceof RegExp)
strout += obj[prop].toString();
else if (obj[prop] instanceof Date)
strout += "new Date(" + JSON.stringify(obj[prop]) + ")";
else if (obj[prop] instanceof Array)
strout += "Array.prototype.slice.call({\n "
+ this.toSourceString(obj[prop], recursion + 1)
+ " length: " + obj[prop].length
+ "\n })";
else
strout += "{\n "
+ this.toSourceString(obj[prop], recursion + 1).replace(/\,\s*$/, '')
+ "\n }";
break;
}
strout += recursion ? ",\n " : ";\n ";
}
}
return strout;
},
evaluate: function(strInput, obj) {
var str = this.toSourceString(obj);
return (new Function(str + 'return ' + strInput))();
}
};
更新
我想出了一个简洁的解决方案来解决这个问题,它的行为类似于 node 的 vm 模块。
var VM = function(o) {
eval((function() {
var src = '';
for (var prop in o) {
if (o.hasOwnProperty(prop)) {
src += 'var ' + prop + '=o[\'' + prop + '\'];';
}
}
return src;
})());
return function() {
return eval(arguments[0]);
}
}
然后可以这样使用:
var vm = new VM({ prop1: { prop2: 3 } });
console.assert(3 === vm('prop1.prop2'), 'Property access');
此解决方案仅使用标识符 arguments
覆盖命名空间。
感谢 Ryan Wheale 的想法。
短版
使用 javascript 对象作为上下文评估自定义 javascript 表达式的最佳方法是什么?
var context = { prop1: { prop2: 3 } }
console.assert(3 === evaluate('prop1.prop2', context), 'Simple expression')
console.assert(3 === evaluate('(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()', context), 'Complex expression')
它应该 运行 在最新版本的节点 (0.12) 和撰写本文时 (3/6/2015) 的所有常绿浏览器上。
注意:大多数模板引擎都支持此功能。例如,Jade.
长版
我目前正在开发一个应用程序引擎,它的一个特点是它接受一段代码并使用提供的对象对其进行评估,然后 return 得出结果。
例如,engine.evaluate('prop1.prop2', {prop1: {prop2: 3}})
应该 return 3
.
这可以通过以下方式轻松完成:
function(code, obj) {
with (obj) {
return eval(code);
}
};
但是,with
的用法已知是一种不好的做法,在 ES5 严格模式下不会 运行。
在查看 with
之前,我已经编写了一个替代解决方案:
function(code, obj) {
return (function() {
return eval(code);
}).call(obj, code);
}
但是,此方法需要使用this
。
如:engine.evaluate('this.prop1.prop2', {prop1: {prop2: 3}})
最终用户不应使用任何 "prefix"。
引擎还必须能够评估像
这样的字符串'prop1.prop2 + 5'
和
'(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()'
以及包含从提供的对象调用函数的那些。
因此,它不能依赖于将 code
字符串单独拆分为 属性 个名称。
这个问题的最佳解决方案是什么?
更新 3:一旦我们弄清楚您真正要问的是什么,问题就很明确了:您不会那样做。特别是在严格模式下。
作为您方法的可行替代方案,请参阅有关 require.js、common.js 和其他允许您在浏览器中加载模块的库的文档。基本上,主要区别在于您不执行 prop1.prop2
而是执行 context.prop1.prop2
。
如果可以使用 context.prop1.prop2
,请参阅 jsfiddle:http://jsfiddle.net/vittore/5rse4jto/
"use strict";
var obj = { prop1 : { prop2: 'a' } }
function evaluate(code, context) {
var f = new Function('ctx', 'return ' + code);
return f(context)
}
alert(evaluate('ctx.prop1.prop2', obj))
alert(evaluate(
'(function() {' +
' console.log(ctx.prop1.prop2);' +
' return ctx.prop1.prop2;' +
'}) ()', obj))
更新:关于如何使用 prop1.prop2
首先,您可以使用字典符号访问您的变量,即:
obj['prop1']['prop2'] === obj.prop1.prop2
给我几分钟时间想出如何递归执行的示例
已更新:这应该有效(这里是 gist):
function jpath_(o, props) {
if (props.length == 1)
return o[props[0]];
return jpath_(o[props.shift()], props)
}
function jpath(o, path) {
return jpath_(o, path.split('.'))
}
console.log(jpath(obj, 'prop1.prop2'))
我不知道你的所有场景,但这应该让你有一个良好的开端:
http://jsfiddle.net/ryanwheale/e8aaa8ny/
var engine = {
evaluate: function(strInput, obj) {
var fnBody = '';
for(var prop in obj) {
fnBody += "var " + prop + "=" + JSON.stringify(obj[prop]) + ";";
}
return (new Function(fnBody + 'return ' + strInput))();
}
};
更新 - 我感到无聊:http://jsfiddle.net/ryanwheale/e8aaa8ny/3/
var engine = {
toSourceString: function(obj, recursion) {
var strout = "";
recursion = recursion || 0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
strout += recursion ? " " + prop + ": " : "var " + prop + " = ";
switch (typeof obj[prop]) {
case "string":
case "number":
case "boolean":
case "undefined":
strout += JSON.stringify(obj[prop]);
break;
case "function":
// won't work in older browsers
strout += obj[prop].toString();
break;
case "object":
if (!obj[prop])
strout += JSON.stringify(obj[prop]);
else if (obj[prop] instanceof RegExp)
strout += obj[prop].toString();
else if (obj[prop] instanceof Date)
strout += "new Date(" + JSON.stringify(obj[prop]) + ")";
else if (obj[prop] instanceof Array)
strout += "Array.prototype.slice.call({\n "
+ this.toSourceString(obj[prop], recursion + 1)
+ " length: " + obj[prop].length
+ "\n })";
else
strout += "{\n "
+ this.toSourceString(obj[prop], recursion + 1).replace(/\,\s*$/, '')
+ "\n }";
break;
}
strout += recursion ? ",\n " : ";\n ";
}
}
return strout;
},
evaluate: function(strInput, obj) {
var str = this.toSourceString(obj);
return (new Function(str + 'return ' + strInput))();
}
};