Подтвердить что ты не робот

Преобразование строки в строку шаблона

Можно ли создать строку шаблона в виде обычной строки

let a="b:${b}";

a затем преобразовать его в строку шаблона

let b=10;
console.log(a.template());//b:10

без eval, new Function и других способов генерации динамического кода?

4b9b3361

Ответ 1

Поскольку ваша строка шаблона должна динамически ссылаться на переменную b (во время выполнения), так что ответ: НЕТ, невозможно обойтись без генерации динамического кода.

Но с eval это довольно просто:

let tpl = eval('`'+a+'`');

Ответ 2

Что вы просите здесь:

//non working code quoted from the question
let b=10;
console.log(a.template());//b:10

в точности эквивалентен (по мощности и, er, безопасности) до eval: возможность взять строку, содержащую код, и выполнить этот код; а также способность исполняемого кода видеть локальные переменные в среде вызывающего абонента.

В JS нет способа для функции видеть локальные переменные в своем вызывающем объекте, если только эта функция не является eval(). Даже Function() не может этого сделать.


Когда вы слышите что-то, называемое "строками шаблонов", идущее на JavaScript, естественно предположить, что это встроенная библиотека шаблонов, например Mustache. Это не так. Это в основном просто интерполяция строк и многострочные строки для JS. Я думаю, что это будет распространенное заблуждение на некоторое время.: (

Ответ 3

Нет, нет способа сделать это без генерации динамического кода.

Однако я создал функцию, которая превратит обычную строку в функцию, которая может быть снабжена картой значений, используя внутренние строки шаблонов.

Сгенерировать шаблон String Gist

/**
 * Produces a function which uses template strings to do simple interpolation from objects.
 * 
 * Usage:
 *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
 * 
 *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
 *    // Logs 'Bryan is now the king of Scotland!'
 */
var generateTemplateString = (function(){
    var cache = {};

    function generateTemplate(template){
        var fn = cache[template];

        if (!fn){
            // Replace ${expressions} (etc) with ${map.expressions}.

            var sanitized = template
                .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                    return `\$\{map.${match.trim()}\}`;
                    })
                // Afterwards, replace anything that not ${map.expressions}' (etc) with a blank string.
                .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

            fn = Function('map', `return \`${sanitized}\``);
        }

        return fn;
    }

    return generateTemplate;
})();

Использование:

var kingMaker = generateTemplateString('${name} is king!');

console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.

Надеюсь, это поможет кому-то. Если вы обнаружите проблему с кодом, пожалуйста, будьте любезны, чтобы обновить Gist.

Ответ 4

В моем проекте я создал что-то вроде этого с ES6:

String.prototype.interpolate = function(params) {
  const names = _.keys(params);
  const vals = _.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}

const template = 'Example text: ${text}';
const result = template.interpolate({
  text: 'Foo Boo'
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

Ответ 5

TL;DR: https://jsfiddle.net/w3jx07vt/

Кажется, что все беспокоятся о доступе к переменным, почему бы просто не передать их? Я уверен, что не будет слишком сложно получить переменный контекст в вызывающем абоненте и передать его. Используйте fooobar.com/questions/4053/..., чтобы получить реквизит из obj. Я не могу проверить вас прямо сейчас, но это должно сработать.

function renderString(str,obj){
    return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}

Испытано. Вот полный код.

function index(obj,is,value) {
    if (typeof is == 'string')
        is=is.split('.');
    if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

function renderString(str,obj){
    return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)})
}

renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas
renderString('abc${a.c}asdas',{a:{c:22,d:55},b:44}) //abc22asdas

Ответ 6

Проблема заключается в том, чтобы иметь функцию, которая имеет доступ к переменным своего вызывающего. Вот почему мы видим, что прямая eval используется для обработки шаблонов. Возможным решением могло бы стать создание функции с формальными параметрами, названными свойствами словаря, и вызовом ее с соответствующими значениями в том же порядке. Альтернативным способом было бы иметь что-то простое:

var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());

И для тех, кто использует компилятор Babel, нам нужно создать закрытие, которое запоминает среду, в которой она была создана:

console.log(new Function('name', 'return `' + message + '`;')(name));

Ответ 7

Вы можете использовать прототип строки, например

String.prototype.toTemplate=function(){
    return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10

Но ответ на исходный вопрос никоим образом.

Ответ 8

В настоящее время я не могу комментировать существующие ответы, поэтому я не могу напрямую комментировать Bryan Raynor отличный ответ. Таким образом, этот ответ будет обновлять свой ответ с небольшой коррекцией.

Короче говоря, его функция не может фактически кэшировать созданную функцию, поэтому она всегда будет воссоздавать, независимо от того, видел ли она шаблон раньше. Вот скорректированный код:

    /**
     * Produces a function which uses template strings to do simple interpolation from objects.
     * 
     * Usage:
     *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
     * 
     *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
     *    // Logs 'Bryan is now the king of Scotland!'
     */
    var generateTemplateString = (function(){
        var cache = {};

        function generateTemplate(template){
            var fn = cache[template];

            if (!fn){
                // Replace ${expressions} (etc) with ${map.expressions}.

                var sanitized = template
                    .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                        return `\$\{map.${match.trim()}\}`;
                    })
                    // Afterwards, replace anything that not ${map.expressions}' (etc) with a blank string.
                    .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

                fn = cache[template] = Function('map', `return \`${sanitized}\``);
            }

            return fn;
        };

        return generateTemplate;
    })();

Ответ 9

Мне нужен этот метод с поддержкой Internet Explorer. Оказалось, что обратные тики не поддерживаются даже IE11. Также; используя eval или эквивалент Function не чувствует себя хорошо.

Для тех, кто уведомляет; Я также использую backticks, но они удаляются компиляторами, такими как babel. Методы, предложенные другими, зависят от них во время выполнения. Как сказано ранее; это проблема в IE11 и ниже.

Итак, вот что я придумал:

function get(path, obj, fb = `$\{${path}}`) {
  return path.split('.').reduce((res, key) => res[key] || fb, obj);
}

function parseTpl(template, map, fallback) {
  return template.replace(/\$\{.+?}/g, (match) => {
    const path = match.substr(2, match.length - 3).trim();
    return get(path, map, fallback);
  });
}

Пример вывода:

const data = { person: { name: 'John', age: 18 } };

parseTpl('Hi ${person.name} (${person.age})', data);
// output: Hi John (18)

parseTpl('Hello ${person.name} from ${person.city}', data);
// output: Hello John from ${person.city}

parseTpl('Hello ${person.name} from ${person.city}', data, '-');
// output: Hello John from -

Ответ 10

@Mateusz Moska, решение отлично работает, но когда я использовал его в React Native (режим сборки), он выдает ошибку: Недопустимый символ `` ', хотя он работает, когда я запускаю его в режим отладки.

Итак, я записал свое собственное решение, используя регулярное выражение.

String.prototype.interpolate = function(params) {
  let template = this
  for (let key in params) {
    template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key])
  }
  return template
}

const template = 'Example text: ${text}',
  result = template.interpolate({
    text: 'Foo Boo'
  })

console.log(result)

Демо: https://es6console.com/j31pqx1p/

ПРИМЕЧАНИЕ.. Поскольку я не знаю первопричины проблемы, я поднял билет в режиме реакции-native repo, https://github.com/facebook/react-native/issues/14107, так что как только они смогут исправить/направить меня примерно одинаково:)

Ответ 11

По-прежнему динамический, но кажется более контролируемым, чем просто использование голого eval:

const vm = require('vm')
const moment = require('moment')


let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}'
let context = {
  hours_worked:[{value:10}],
  hours_worked_avg_diff:[{value:10}],

}


function getDOW(now) {
  return moment(now).locale('es').format('dddd')
}

function gt0(_in, tVal, fVal) {
  return _in >0 ? tVal: fVal
}



function templateIt(context, template) {
  const script = new vm.Script('`'+template+'`')
  return script.runInNewContext({context, fns:{getDOW, gt0 }})
}

console.log(templateIt(context, template))

https://repl.it/IdVt/3

Ответ 12

Подобно Daniel K answer (и s.meijer gist), но более читаемым:

const regex = /\${[^{]+}/g;

export default function interpolate(template, variables, fallback) {
    return template.replace(regex, (match) => {
        const path = match.slice(2, -1).trim();
        return getObjPath(path, variables, fallback);
    });
}

//get the specified property or nested property of an object
const getObjPath = (path, obj, fallback = '') => {
    return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}

Примечание. Это немного улучшает оригинал s.meijer, поскольку он не будет соответствовать таким вещам, как ${foo{bar} (регулярное выражение допускает только не фигурные фигурные скобки внутри ${ и }).


UPDATE: меня попросили использовать пример, поэтому здесь вы:

const replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.`, replacements)

Ответ 13

Это решение работает без ES6:

function render(template, opts) {
  return new Function(
    'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' +
    ').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');'
  )();
}

render("hello ${ name }", {name:'mo'}); // "hello mo"

Примечание: конструктор Function всегда создается в глобальной области видимости, что потенциально может привести к тому, что глобальные переменные будут перезаписаны шаблоном, например. render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

Ответ 14

Мне понравился ответ s.meijer и написал мою собственную версию, основанную на его:

function parseTemplate(template, map, fallback) {
    return template.replace(/\$\{[^}]+\}/g, (match) => 
        match
            .slice(2, -1)
            .trim()
            .split(".")
            .reduce(
                (searchObject, key) => searchObject[key] || fallback || match,
                map
            )
    );
}

Ответ 15

Поскольку мы изобретаем колесо на чем-то, что было бы прекрасной функцией в javascript.

Я использую eval(), который не является безопасным, но javascript не защищен. Я с готовностью признаю, что я не превосходна с javascript, но у меня была необходимость, и мне нужен был ответ, поэтому я сделал один.

Я решил стилизовать свои переменные с помощью @, а не $, особенно потому, что я хочу использовать многострочную функцию литералов, не оценивая ее до готовности. Таким образом, переменный синтаксис @{OptionalObject.OptionalObjectN.VARIABLE_NAME}

Я не эксперт по javascript, поэтому я с радостью принимаю советы по улучшению, но...

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.length; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

Очень простая реализация следует

myResultSet = {totalrecords: 2,
Name: ["Bob", "Stephanie"],
Age: [37,22]};

rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.`

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.totalrecords; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}