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

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

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

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

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

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


person KOLANICH    schedule 21.03.2015    source источник
comment
вы нашли способ добиться этого? Возможно, однажды мне понадобится это сделать, и мне любопытно узнать, к чему вы пришли.   -  person Bryan Rayner    schedule 14.08.2015
comment
@BryanRayner позволяет сказать, что ваша js-программа пытается получить данные из rest API, URL-адрес которого находится в файле config.js в виде строки / resources / ‹resource_id› / update /, и вы динамически помещаете resource_id из своей программы. Если вы не хотите разделить этот URL-адрес на части и сохранить в разных областях, вам потребуется какая-то обработка строкового шаблона.   -  person Ryu_hayabusa    schedule 27.12.2016
comment
stackoverflow.com/questions/57565794/   -  person Trevor    schedule 20.08.2019
comment
Вместо использования eval лучше использовать регулярное выражение Eval, это не рекомендуется и крайне не рекомендуется, поэтому, пожалуйста, не используйте его developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…! пусть b = 10; пусть a = b: $ {b}; пусть response = a.replace (/ \ $ {\ w +} /, b); conssole.log (ответ);   -  person Vijay Palaskar    schedule 16.07.2020
comment
@Ryu_hayabusa Я считаю, что цель здесь - иметь возможность ссылаться на эти значения переменных без предварительной обработки шаблона, чтобы мы могли манипулировать ими вживую, а не только на стороне сервера.   -  person Brandon McConnell    schedule 03.06.2021
comment
@KOLANICH на второй вопрос Брайана, тебе когда-нибудь удавалось это понять? Мне это нужно для проекта, над которым я сейчас работаю. В настоящее время я использую new Function(), но не хочу этого делать из-за проблем с XSS.   -  person Brandon McConnell    schedule 03.06.2021


Ответы (18)


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

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

let tpl = eval('`'+a+'`');
person alexpods    schedule 21.03.2015
comment
eval небезопасен, как и другие средства генерации динамического кода - person KOLANICH; 22.03.2015
comment
@KOLANICH Для конкретного случая - избегайте обратных кавычек в строке a, и это будет намного менее небезопасно: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. Я думаю, что более важным является то, что eval помешает компилятору оптимизировать ваш код. Но я думаю, что это не имеет отношения к этому вопросу. - person alexpods; 22.03.2015
comment
Но строки шаблона не предназначены для создания пользователем, поэтому с безопасностью этого метода все в порядке. Но в любом случае мне это не нравится. - person KOLANICH; 22.03.2015
comment
Фактически, вы также можете запускать функции внутри строк шаблона. - person KOLANICH; 22.03.2015
comment
@KOLANICH Да. Но когда вы написали вопрос, не следует ли вам предлагать следующее: let a = "b:${sumFunc()}"; console.log(a.template()); будет запускать sumFunc функцию при создании строки шаблона? - person alexpods; 22.03.2015
comment
На самом деле я не ожидал такого поведения, когда задал вопрос. - person KOLANICH; 22.03.2015
comment
@KOLANICH Извините, вам не нравится eval. Однако помните, что литерал шаблона сам по себе является формой eval. Два примера: var test = Result: ${alert('hello')}; var test = Result: ${b=4}; Оба в конечном итоге выполнят произвольный код в контексте сценария. Если вы хотите разрешить произвольные строки, вы можете также разрешить eval. - person Manngo; 14.08.2016
comment
Будь осторожен. Поскольку что-то вроде babel не будет переносить это, этот код НЕ будет работать в IE. - person cgsd; 09.01.2017
comment
вы можете немного сократить его, если заранее добавите `` в исходную строку: пример let string = '`my is ${name}`' - person pery mimon; 11.03.2018
comment
Вместо использования eval лучше использовать для регулярного выражения Eval, это не рекомендуется и крайне не рекомендуется, поэтому, пожалуйста, не используйте его developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/! пусть b = 10; пусть a = b: $ {b}; пусть response = a.replace (/ \ $ {\ w +} /, b); conssole.log (ответ); - person Vijay Palaskar; 16.07.2020
comment
Это крайне небезопасно, будьте осторожны: const templateJsInjection = '1` && (console.log (injected) || 1) && `1' - person Paweł Siemienik; 11.02.2021

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

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

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

ОБНОВЛЕНИЕ. Я удалил зависимость lodash, в ES6 есть эквивалентные методы для получения ключей и значений.

person Mateusz Moska    schedule 07.12.2016
comment
Привет, ваше решение отлично работает, но когда я использовал его в React Native (режим сборки), оно выдает ошибку: Недопустимый символ '' ', хотя оно работает, когда я запускаю в режиме отладки. Похоже, проблема с бабелем, есть ли помощь? - person Mohit Pandey; 22.05.2017
comment
@MohitPandey Я получал ту же ошибку, когда запускал тесты этого кода под PhantomJS, и он проходил под chrome. Если это так, я думаю, что скоро появится новая бета-версия PhantomJS с улучшенной поддержкой ES6, вы можете попробовать ее установить. - person Mateusz Moska; 23.05.2017
comment
К сожалению, это не работает, и я написал для него регулярное выражение. Также добавлен как ответ. - person Mohit Pandey; 23.05.2017
comment
это решение работает только в том случае, если символ обратной галочки `отсутствует в строке шаблона - person SliverNinja - MSFT; 19.09.2017
comment
Когда я пробовал это, я получил ReferenceError: _ is not defined. Это код не для ES6, а для lodash, или ...? - person xpt; 19.02.2018
comment
@xpt спасибо за ваш комментарий. Зависимость от lodash действительно была, я ее удалил, проверьте обновление. - person Mateusz Moska; 21.02.2018
comment
Работает как шарм. Спасибо, Матеуш! - person xpt; 21.02.2018
comment
Выдает ошибку «SyntaxError: восьмеричные escape-последовательности не разрешены в строгом режиме». когда в шаблоне есть обратная косая черта. - person aleung; 15.06.2018
comment
@aleung, это решение определенно не является полностью пуленепробиваемым, могут быть некоторые крайние случаи, когда оно выдает исключения. Может быть, попробуйте двойную обратную косую черту с escape-символом. Вы также можете поставить там некоторую защиту, чтобы избежать критических символов, таких как обратная косая черта или обратная косая черта. - person Mateusz Moska; 15.06.2018
comment
не работает, если json replacer содержит целочисленное значение - person uray; 07.12.2018
comment
Это очень интересная альтернатива использованию with (params) eval('`'+str.replace(/`/g,'\\`')+'`');. - person Roy Tinker; 10.08.2019
comment
Отличное решение! Обратите внимание, что это также работает с передачей вложенных объектов в качестве параметра! Вот пример: учитывая вложенный объект: parent = {child: {a: "1", b: "2"}} и строковый литерал: s = "${parent.child.a}${parent.child.b}${parent.child.a}${parent.child.b}", тогда s.interpolate(parent) вернет "1212" - person Logan Besecker; 07.10.2019
comment
Если вы получили эту ошибку, Arg string terminates parameters early проверьте этот ответ stackoverflow.com/a/59410199/6478359 - person Muhammet Can TONBUL; 19.12.2019
comment
Это решение намного лучше, чем eval. eval следует забанить. - person Benjam; 04.06.2020
comment
Это решение отлично работает для меня, но выглядит немного взломанным. Насколько безопасно использовать его в производстве? - person L. Langó; 26.06.2020
comment
Хммм ..... Пытаюсь сделать так: var cloneHtml = clone.outerHTML.interpolate(viewerData); и получаю TypeError: Cannot read property 'interpolate' of undefined. Я объявил String.prototype.interpolate именно так, как показано выше .... - person Antonio Ooi; 08.08.2020
comment
Извините, ребята, теперь это работает: document.querySelector("template").innerHTML.interpolate(viewerData); - person Antonio Ooi; 08.08.2020
comment
Это потрясающе, не могли бы вы объяснить или дать какую-нибудь ссылку, чтобы понять это ..... return new Function(...names, `return `${this}`;`)(...vals); - person mukuljainx; 04.04.2021
comment
@mukuljainx Проверьте, как работает «новая функция»: javascript.info/new-function. В основном, приведенный выше пример делает следующее: (new Function('text', 'return `Example text: ${text}`;'))('Foo Boo'); - person Mateusz Moska; 05.04.2021
comment
Ничего не стоит то, что, хотя это улучшение по сравнению с eval(), поскольку оно не пропускает вашу область видимости, _ 2_ конструктор действительно разделяет некоторые проблемы безопасности с eval(), поскольку он все еще оценивает строку как JavaScript, и к нему следует относиться с аналогичной осторожностью. Документация MDN по никогда не использовать eval () более подробно рассказывает об этом. - person cincodenada; 10.04.2021
comment
Да, и для дальнейшей поддержки комментария @cincodenada любые браузеры или веб-серверы, блокирующие XSS, имеют тенденцию блокировать как eval(), так и new Function(). - person Brandon McConnell; 03.06.2021

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

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

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

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


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

person Jason Orendorff    schedule 23.03.2015
comment
ТБХ, вот что я думал. Было бы очень и очень удобно. - person Bryan Rayner; 14.08.2015
comment
Это (все еще) работает? Я получаю template is not a function. - person Ionică Bizău; 31.12.2015
comment
Блок кода в верхней части этого ответа - это цитата из вопроса. Это не работает. - person Jason Orendorff; 06.01.2016

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

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

Создать шаблон строки 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's 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.

person Bryan Rayner    schedule 13.08.2015
comment
Спасибо! Я использовал это вместо решения javascript sprintf. - person seangwright; 14.04.2016
comment
не работает для всех шаблонов var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'})) return /api/bar// - person Guillaume Vincent; 29.07.2016
comment
Спасибо, исправлено. Регулярное выражение включало одно совпадение $ {param1} / $ {param2}, тогда как должно было быть два совпадения. - person Bryan Rayner; 29.07.2016
comment
Обратите внимание, что это не работает в IE11 из-за отсутствия поддержки обратных тиков. - person s.meijer; 17.01.2017
comment
Конечно, если строки шаблона не поддерживаются браузером, этот метод не сработает. Если вы хотите использовать строки шаблона в неподдерживаемых браузерах, я бы рекомендовал использовать такой язык, как TypeScript, или транспилятор, такой как Babel; Это единственный способ внедрить ES6 в старые браузеры. - person Bryan Rayner; 17.01.2017
comment
Обратите внимание, что это не будет передавать правила ESLint в некоторых базах кода, особенно если no-new- Правило func включено. Конструктор Function работает немного иначе (то есть немного безопаснее), чем eval, но некоторые программисты предпочитают полностью избегать его по тем же причинам (отладка, безопасность). - person Matt Browne; 17.11.2017
comment
Заявление об отказе от ответственности - предложенное мной решение работает - но это не значит, что я его рекомендую. На мой взгляд, в тот момент, когда вы пытаетесь внедрить динамические шаблоны в свое приложение, вам лучше создать собственный DSL. - person Bryan Rayner; 28.11.2017
comment
Работает как чамер! - person Jasson Rojas; 08.08.2018

Здесь опубликовано много хороших решений, но пока ни одно из них не использует метод ES6 String.raw. Вот мой вклад. У него есть важное ограничение в том, что он принимает свойства только от переданного объекта, что означает, что выполнение кода в шаблоне не будет работать.

function parseStringTemplate(str, obj) {
    let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
    let args = str.match(/[^{\}]+(?=})/g) || [];
    let parameters = args.map(argument => obj[argument] || (obj[argument] === undefined ? "" : obj[argument]));
    return String.raw({ raw: parts }, ...parameters);
}
let template = "Hello, ${name}! Are you ${age} years old?";
let values = { name: "John Doe", age: 18 };

parseStringTemplate(template, values);
// output: Hello, John Doe! Are you 18 years old?
  1. Разделить строку на текстовые части без аргументов. См. регулярное выражение.
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Разделить строку на имена свойств. Пустой массив, если сопоставление не удалось.
    args: ["name", "age"]
  3. Сопоставьте параметры из obj по имени свойства. Решение ограничено мелким одноуровневым отображением. Неопределенные значения заменяются пустой строкой, но допускаются другие ложные значения.
    parameters: ["John Doe", 18]
  4. Используйте String.raw(...) и верните результат.
person pekaaw    schedule 28.11.2019
comment
Из любопытства, какое значение на самом деле предоставляет String.raw? Кажется, вы делаете всю работу по синтаксическому анализу строки и отслеживаете, что это за подстановки. Сильно ли это отличается от простого повторного вызова .replace()? - person Steve Bennett; 12.05.2020
comment
Справедливый вопрос, @SteveBennett. У меня были проблемы с преобразованием обычной строки в строку шаблона, и я нашел решение, построив необработанный объект сам. Я предполагаю, что это сводит String.raw к методу конкатенации, но я думаю, что он работает довольно хорошо. Я бы хотел увидеть хорошее решение с .replace() :) Я считаю, что удобочитаемость важна, поэтому, используя регулярные выражения, я стараюсь давать им имена, чтобы помочь разобраться во всем этом ... - person pekaaw; 13.05.2020

TL; DR: https://jsfiddle.net/bj89zntu/1/

Кажется, всех беспокоит доступ к переменным, почему бы просто не передать их? Я уверен, что получить контекст переменной в вызывающей программе и передать ее вниз не будет слишком сложно. Используйте этот https://stackoverflow.com/a/6394168/6563504, чтобы получить реквизиты из 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
person M3D    schedule 09.01.2017
comment
@ s.meijer не могли бы вы уточнить? Я успешно использую этот код. jsfiddle.net/w3jx07vt - person M3D; 19.01.2017
comment
Лучшее регулярное выражение позволит вам не выбирать символы ${}. Попробуйте: /(?!\${)([^{}]*)(?=})/g - person Eric Hodonsky; 03.07.2017
comment
@Relic jsfiddle.net/w3jx07vt/2 Я не мог заставить это работать, позаботьтесь о предоставлении рука и я обновлю свой пост? :) - person M3D; 05.07.2017
comment
То, как вы пытаетесь это схватить, на самом деле не очень поможет, я вместо этого сделал замену строки. Вместо добавления шага интерполяции, я могу использовать строку как интерполяцию или строку. Непонятно, но это сработало. - person Eric Hodonsky; 07.07.2017
comment
@EricHodonsky Не могли бы вы уточнить, как я пытаюсь это схватить? Возможно, поделитесь своим вариантом использования. Большинство вышеперечисленных решений ориентированы на генерацию кода, в которой мое решение не требуется. Проблема здесь: A) доступ к переменным (чего я не делаю, позволяя пользователю передавать их вместо этого), B) поиск мест для замены имен, C) поиск имен (в данном случае на объекте). Это работает согласно jsfiddle. - person M3D; 10.01.2021

Проблема здесь в том, чтобы иметь функцию, которая имеет доступ к переменным вызывающей стороны. Вот почему мы видим, что прямое 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));
person didinko    schedule 28.11.2015
comment
Ваш первый фрагмент на самом деле хуже, чем eval, потому что он работает только с глобальной переменной name - person Bergi; 29.11.2015
comment
@Bergi Ваше утверждение действительно - объем функции будет утерян. Я хотел представить простое решение проблемы и привел упрощенный пример того, что можно сделать. Чтобы решить эту проблему, можно просто придумать следующее: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ message +' ;')();} - person didinko; 29.11.2015
comment
Нет, это именно то, что не сработает - new Function не имеет доступа к var name в функции template. - person Bergi; 29.11.2015
comment
Второй фрагмент исправил мою проблему ... Я проголосовал за! Спасибо, это помогло решить временную проблему с динамической маршрутизацией на iframe :) - person Kris Boyd; 20.07.2017

Мне понравился ответ 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
            )
    );
}
person Daniel    schedule 01.02.2017
comment
Аккуратный! Действительно аккуратно! - person xpt; 20.02.2018
comment
Почему редуктор? - person KooiInc; 22.12.2020
comment
вместо searchObject[key] мне пришлось использовать searchObject.hasOwnProperty(key) для поддержки ложных значений. - person cyrf; 24.05.2021

Подобно ответу Дэниела (и gist) s.meijer более читабельно:

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
function getObjPath(path, obj, fallback = '') {
    return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}

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


ОБНОВЛЕНИЕ: меня попросили привести пример использования этого, так что давайте:

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

interpolate('My name is ${name}, and I am ${age}.', replacements)
person Matt Browne    schedule 17.11.2017
comment
Можете ли вы опубликовать пример, действительно использующий это? Этот javascript мне немного недоступен. Я бы предложил регулярное выражение /\$\{(.*?)(?!\$\{)\}/g (для обработки фигурных скобок). У меня есть рабочее решение, но я не уверен, что оно такое портативное, как ваше, поэтому мне бы хотелось посмотреть, как это должно быть реализовано на странице. Мой также использует eval(). - person Regular Jo; 17.01.2018
comment
Я пошел дальше и опубликовал ответ, и мне бы хотелось получить ваши отзывы о том, как сделать это более безопасным и ориентированным на производительность: stackoverflow.com / а / 48294208. - person Regular Jo; 17.01.2018
comment
@RegularJoe Я добавил пример. Моей целью было сделать его простым, но вы правы в том, что если вы хотите обрабатывать вложенные фигурные скобки, вам нужно будет изменить регулярное выражение. Однако я не могу придумать для этого варианта использования при оценке обычной строки, как если бы это был литерал шаблона (вся цель этой функции). Что у тебя было на уме? - person Matt Browne; 18.01.2018
comment
Кроме того, я не являюсь ни экспертом по производительности, ни экспертом по безопасности; мой ответ на самом деле просто объединяет два предыдущих ответа. Но я скажу, что использование eval оставляет вас гораздо более уязвимым для возможных ошибок, которые могут вызвать проблемы с безопасностью, тогда как все, что делает моя версия, - это поиск свойства объекта по пути, разделенному точками, что должно быть безопасным. - person Matt Browne; 18.01.2018

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

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

Но ответить на исходный вопрос нельзя.

person sarkiroka    schedule 10.07.2016

@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/

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

person Mohit Pandey    schedule 23.05.2017
comment
это поддерживает шаблоны, содержащие символ обратной галочки. Однако, чем пытаться изобрести шаблон, вам, вероятно, лучше просто использовать усы или аналогичные. в зависимости от того, насколько сложны ваши шаблоны, это метод грубой силы, который не учитывает крайние случаи - ключ может содержать специальный шаблон регулярного выражения. - person SliverNinja - MSFT; 19.09.2017

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

Для того, кто замечает; Я также использую обратные кавычки, но они удаляются такими компиляторами, как 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 -
person s.meijer    schedule 17.01.2017
comment
использование eval или его эквивалентной функции кажется неправильным. ... Да ... Я согласен, но я думаю, что это один из очень немногих вариантов использования, в которых можно было бы сказать, ммк, давайте использовать его. Пожалуйста, проверьте jsperf.com/es6-string-tmpl - это мой практический пример использования. Используя вашу функцию (с тем же регулярным выражением, что и у меня) и мою (eval + строковые литералы). Спасибо! :) - person Andrea Puddu; 21.02.2017
comment
@AndreaPuddu, у тебя действительно лучше. Но опять же; строки шаблонов не поддерживаются в IE. Так что eval('`' + taggedURL + '`') просто не работает. - person s.meijer; 21.02.2017
comment
Кажется, лучше я бы сказал, потому что он тестировался изолированно ... Единственная цель этого теста заключалась в том, чтобы увидеть потенциальные проблемы с производительностью с использованием eval. Что касается шаблонных литералов: спасибо, что снова на это указали. Я использую Babel для транспиляции моего кода, но моя функция по-прежнему не будет работать ???? - person Andrea Puddu; 22.02.2017

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

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

    /**
     * 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's not ${map.expressions}' (etc) with a blank string.
                    .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

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

            return fn;
        };

        return generateTemplate;
    })();
person user2501097    schedule 04.12.2016

По-прежнему динамичен, но кажется более управляемым, чем просто использование простого 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

person Robert Moskal    schedule 09.06.2017

Это решение работает без 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'});

person cruzanmo    schedule 06.01.2017

Вам следует попробовать этот крошечный модуль JS от Андреа Джаммарчи с github: https://github.com/WebReflection/backtick-template

/*! (C) 2017 Andrea Giammarchi - MIT Style License */
function template(fn, $str, $object) {'use strict';
  var
    stringify = JSON.stringify,
    hasTransformer = typeof fn === 'function',
    str = hasTransformer ? $str : fn,
    object = hasTransformer ? $object : $str,
    i = 0, length = str.length,
    strings = i < length ? [] : ['""'],
    values = hasTransformer ? [] : strings,
    open, close, counter
  ;
  while (i < length) {
    open = str.indexOf('${', i);
    if (-1 < open) {
      strings.push(stringify(str.slice(i, open)));
      open += 2;
      close = open;
      counter = 1;
      while (close < length) {
        switch (str.charAt(close++)) {
          case '}': counter -= 1; break;
          case '{': counter += 1; break;
        }
        if (counter < 1) {
          values.push('(' + str.slice(open, close - 1) + ')');
          break;
        }
      }
      i = close;
    } else {
      strings.push(stringify(str.slice(i)));
      i = length;
    }
  }
  if (hasTransformer) {
    str = 'function' + (Math.random() * 1e5 | 0);
    if (strings.length === values.length) strings.push('""');
    strings = [
      str,
      'with(this)return ' + str + '([' + strings + ']' + (
        values.length ? (',' + values.join(',')) : ''
      ) + ')'
    ];
  } else {
    strings = ['with(this)return ' + strings.join('+')];
  }
  return Function.apply(null, strings).apply(
    object,
    hasTransformer ? [fn] : []
  );
}

template.asMethod = function (fn, object) {'use strict';
  return typeof fn === 'function' ?
    template(fn, this, object) :
    template(this, fn);
};

Демо (верны все следующие тесты):

const info = 'template';
// just string
`some ${info}` === template('some ${info}', {info});

// passing through a transformer
transform `some ${info}` === template(transform, 'some ${info}', {info});

// using it as String method
String.prototype.template = template.asMethod;

`some ${info}` === 'some ${info}'.template({info});

transform `some ${info}` === 'some ${info}'.template(transform, {info});
person colxi    schedule 12.07.2018

Я сделал собственное решение, сделав тип с описанием как функцией

export class Foo {
...
description?: Object;
...
}

let myFoo:Foo = {
...
  description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}

и так поступаем:

let myDescription = myFoo.description('Bar', 'bar');
person cesarmarinhorj    schedule 08.11.2019

Поскольку мы изобретаем колесо для чего-то, что было бы прекрасной функцией в 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);
}

В моей реальной реализации я предпочитаю использовать @{{variable}}. Еще один комплект подтяжек. Нелепо вряд ли столкнусь с этим неожиданно. Регулярное выражение для этого будет выглядеть как /\@\{\{(.*?)(?!\@\{\{)\}\}/g

Чтобы было легче читать

\@\{\{    # opening sequence, @{{ literally.
(.*?)     # capturing the variable name
          # ^ captures only until it reaches the closing sequence
(?!       # negative lookahead, making sure the following
          # ^ pattern is not found ahead of the current character
  \@\{\{  # same as opening sequence, if you change that, change this
)
\}\}      # closing sequence.

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

person Regular Jo    schedule 17.01.2018