Как оценить пользовательское выражение 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'))
Другие вопросы по тегам