Как оценить пользовательское выражение JavaScript с контекстом в строгом режиме?
Обновить
Я придумал краткое решение этой проблемы, которое ведет себя подобно модулю 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
приняты.
Спасибо Райану Уилу за его идею.
Укороченная версия
Каков наилучший способ оценить пользовательское выражение 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).
Примечание. Большинство шаблонизаторов поддерживают эту функцию. Например, Джейд.
Длинная версия
В настоящее время я работаю над механизмом приложения, и одна из его функций заключается в том, что он берет кусок кода, оценивает его с помощью предоставленного объекта и возвращает результат.
Например, engine.evaluate('prop1.prop2', {prop1: {prop2: 3}})
должен вернуться 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}})
Конечный пользователь не должен использовать какой-либо "префикс".
Двигатель также должен уметь оценивать такие строки, как
'prop1.prop2 + 5'
а также
'(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()'
и те, которые содержат вызовы функций из предоставленного объекта.
Таким образом, он не может полагаться на разделение code
Строка в имена свойств в одиночку.
Как лучше всего решить эту проблему?
2 ответа
Я не знаю всех ваших сценариев, но это должно дать вам преимущество:
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))();
}
};
ОБНОВЛЕНИЕ 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
Дайте мне несколько минут, чтобы придумать пример того, как сделать это рекурсивно
ОБНОВЛЕНО: Это должно работать (вот суть):
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'))