Можно ли создать строку шаблона как обычную строку
let a="b:${b}";
а затем преобразовать его в строку шаблона
let b=10;
console.log(a.template());//b:10
без eval
, new Function
и других средств динамической генерации кода?
Можно ли создать строку шаблона как обычную строку
let a="b:${b}";
а затем преобразовать его в строку шаблона
let b=10;
console.log(a.template());//b:10
без eval
, new Function
и других средств динамической генерации кода?
Поскольку ваша строка шаблона должна получать ссылку на переменную b
динамически (во время выполнения), поэтому ответ: НЕТ, это невозможно сделать без генерации динамического кода.
Но с eval
это довольно просто:
let tpl = eval('`'+a+'`');
a
, и это будет намного менее небезопасно: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');
. Я думаю, что более важным является то, что eval
помешает компилятору оптимизировать ваш код. Но я думаю, что это не имеет отношения к этому вопросу.
- person alexpods; 22.03.2015
let a = "b:${sumFunc()}"; console.log(a.template());
будет запускать sumFunc
функцию при создании строки шаблона?
- person alexpods; 22.03.2015
eval
. Однако помните, что литерал шаблона сам по себе является формой eval
. Два примера: var test = Result: ${alert('hello')}
; var test = Result: ${b=4}
; Оба в конечном итоге выполнят произвольный код в контексте сценария. Если вы хотите разрешить произвольные строки, вы можете также разрешить eval
.
- person Manngo; 14.08.2016
let string = '`my is ${name}`'
- person pery mimon; 11.03.2018
В моем проекте я создал что-то вроде этого с помощью 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 есть эквивалентные методы для получения ключей и значений.
ReferenceError: _ is not defined
. Это код не для ES6, а для lodash
, или ...?
- person xpt; 19.02.2018
with (params) eval('`'+str.replace(/`/g,'\\`')+'`');
.
- person Roy Tinker; 10.08.2019
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
Arg string terminates parameters early
проверьте этот ответ stackoverflow.com/a/59410199/6478359
- person Muhammet Can TONBUL; 19.12.2019
var cloneHtml = clone.outerHTML.interpolate(viewerData);
и получаю TypeError: Cannot read property 'interpolate' of undefined
. Я объявил String.prototype.interpolate
именно так, как показано выше ....
- person Antonio Ooi; 08.08.2020
document.querySelector("template").innerHTML.interpolate(viewerData);
- person Antonio Ooi; 08.08.2020
return new Function(...names, `return `${this}`;`)(...vals);
- person mukuljainx; 04.04.2021
(new Function('text', 'return `Example text: ${text}`;'))('Foo Boo');
- person Mateusz Moska; 05.04.2021
eval()
, поскольку оно не пропускает вашу область видимости, _ 2_ конструктор действительно разделяет некоторые проблемы безопасности с eval()
, поскольку он все еще оценивает строку как JavaScript, и к нему следует относиться с аналогичной осторожностью. Документация MDN по никогда не использовать eval () более подробно рассказывает об этом.
- person cincodenada; 10.04.2021
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. Я думаю, что какое-то время это будет распространенным заблуждением. :(
template is not a function
.
- person Ionică Bizău; 31.12.2015
Нет, это невозможно сделать без динамической генерации кода.
Однако я создал функцию, которая превратит обычную строку в функцию, которая может быть снабжена картой значений, используя внутренние строки шаблона.
/**
* 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.
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
return /api/bar//
- person Guillaume Vincent; 29.07.2016
Function
работает немного иначе (то есть немного безопаснее), чем eval
, но некоторые программисты предпочитают полностью избегать его по тем же причинам (отладка, безопасность).
- person Matt Browne; 17.11.2017
Здесь опубликовано много хороших решений, но пока ни одно из них не использует метод 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?
parts: ["Hello, ", "! Are you ", " years old?"]
args: ["name", "age"]
obj
по имени свойства. Решение ограничено мелким одноуровневым отображением. Неопределенные значения заменяются пустой строкой, но допускаются другие ложные значения. parameters: ["John Doe", 18]
String.raw(...)
и верните результат..replace()
?
- person Steve Bennett; 12.05.2020
.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
${}
. Попробуйте: /(?!\${)([^{}]*)(?=})/g
- person Eric Hodonsky; 03.07.2017
Проблема здесь в том, чтобы иметь функцию, которая имеет доступ к переменным вызывающей стороны. Вот почему мы видим, что прямое 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));
eval
, потому что он работает только с глобальной переменной name
- person Bergi; 29.11.2015
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
new Function
не имеет доступа к var name
в функции template
.
- person Bergi; 29.11.2015
Мне понравился ответ 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
)
);
}
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)
/\$\{(.*?)(?!\$\{)\}/g
(для обработки фигурных скобок). У меня есть рабочее решение, но я не уверен, что оно такое портативное, как ваше, поэтому мне бы хотелось посмотреть, как это должно быть реализовано на странице. Мой также использует eval()
.
- person Regular Jo; 17.01.2018
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
Но ответить на исходный вопрос нельзя.
@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, чтобы однажды они могли исправить / направить меня примерно так же :)
Мне потребовался этот метод с поддержкой 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 -
eval('`' + taggedURL + '`')
просто не работает.
- person s.meijer; 21.02.2017
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;
})();
По-прежнему динамичен, но кажется более управляемым, чем просто использование простого 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))
Это решение работает без 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'});
Вам следует попробовать этот крошечный модуль 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});
Я сделал собственное решение, сделав тип с описанием как функцией
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');
Поскольку мы изобретаем колесо для чего-то, что было бы прекрасной функцией в 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.
Если у вас нет опыта работы с регулярными выражениями, довольно безопасное правило - экранировать все не буквенно-цифровые символы и не никогда без нужды экранировать буквы, поскольку многие экранированные буквы имеют особое значение практически для всех разновидностей регулярных выражений. .
new Function()
, но не хочу этого делать из-за проблем с XSS. - person Brandon McConnell   schedule 03.06.2021