Отложить выполнение для шаблонов шаблонов ES6
Я играю с новой функцией ES6 Template Literals, и первое, что мне пришло в голову, это String.format
для JavaScript, поэтому я String.format
к реализации прототипа:
String.prototype.format = function() {
var self = this;
arguments.forEach(function(val,idx) {
self["p"+idx] = val;
});
return this.toString();
};
console.log('Hello, ${p0}. This is a ${p1}'.format("world", "test"));
ES6Fiddle
Тем не менее, шаблон Literal оценивается до того, как он передается моему методу-прототипу. Можно ли как-нибудь написать приведенный выше код, чтобы отложить результат до тех пор, пока я динамически не создаю элементы?
Ответы
Ответ 1
Я вижу три способа обойти это:
-
Используйте строки шаблонов, как они были предназначены для использования, без функции format
:
console.log(`Hello, ${"world"}. This is a ${"test"}`);
// might make more sense with variables:
var p0 = "world", p1 = "test";
console.log(`Hello, ${p0}. This is a ${p1}`);
// or even function parameters for actual deferral of the evaluation:
const welcome = (p0, p1) => `Hello, ${p0}. This is a ${p1}`;
console.log(welcome("world", "test"));
-
Не используйте строку шаблона, но простой литерал строки:
String.prototype.format = function() {
var args = arguments;
return this.replace(/\$\{p(\d)\}/g, function(match, id) {
return args[id];
});
};
console.log("Hello, ${p0}. This is a ${p1}".format("world", "test"));
-
Используйте тегированный тег шаблона. Обратите внимание, что подстановки все равно будут оцениваться без перехвата обработчиком, поэтому вы не можете использовать идентификаторы типа p0
, не имея такой переменной. Такое поведение может измениться, если принято предложение синтаксиса для синтаксиса (обновление: оно не было).
function formatter(literals, ...substitutions) {
return {
format: function() {
var out = [];
for(var i=0, k=0; i < literals.length; i++) {
out[k++] = literals[i];
out[k++] = arguments[substitutions[i]];
}
out[k] = literals[i];
return out.join("");
}
};
}
console.log(formatter`Hello, ${0}. This is a ${1}`.format("world", "test"));
// Notice the number literals: ^ ^
Ответ 2
Мне также нравится идея функции String.format
и возможность явного определения переменных для разрешения.
Это то, что я придумал... в основном метод String.replace
с deepObject
.
const isUndefined = o => typeof o === 'undefined'
const nvl = (o, valueIfUndefined) => isUndefined(o) ? valueIfUndefined : o
// gets a deep value from an object, given a 'path'.
const getDeepValue = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj)
// given a string, resolves all template variables.
const resolveTemplate = (str, variables) => {
return str.replace(/\$\{([^\}]+)\}/g, (m, g1) =>
nvl(getDeepValue(variables, g1), m))
}
// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
return resolveTemplate(this, variables)
}
// setup variables for resolution...
var variables = {}
variables['top level'] = 'Foo'
variables['deep object'] = {text:'Bar'}
var aGlobalVariable = 'Dog'
// ==> Foo Bar <==
console.log('==> ${top level} ${deep object.text} <=='.format(variables))
// ==> Dog Dog <==
console.log('==> ${aGlobalVariable} ${aGlobalVariable} <=='.format(this))
// ==> ${not an object.text} <==
console.log('==> ${not an object.text} <=='.format(variables))
Ответ 3
Я опубликовал ответ на аналогичный вопрос, который дает два подхода, где выполнение литерала шаблона задерживается. Когда литерал шаблона находится в функции, литерал шаблона оценивается только при вызове функции и оценивается с использованием области действия функции.
fooobar.com/questions/457867/...
Ответ 4
Вот решение, которое я придумал, используя конструктор Function
. Нет необходимости в регулярных выражениях или eval.
Вспомогательная функция:
const formatMessage = ({ message, values = {} }) => {
return new Function(...Object.keys(values), 'return \'${message}\'')(...Object.values(values));
};
Использование:
formatMessage({
message: "This is a formatted message for ${NAME}.",
values: { NAME: "Bob" }
});
// Output: This is a formatted message for Bob.
Вы можете попытаться применить ту же логику в своей функции-прототипе.
Ответ 5
Расширяя ответ @Bergi, сила помеченных шаблонных строк раскрывается, когда вы понимаете, что в результате вы можете вернуть что угодно, а не только простые строки. В его примере тег создает и возвращает объект с format
свойства замыкания и функции.
В моем любимом подходе я сам возвращаю значение функции, которое вы можете вызвать позже и передать новые параметры для заполнения шаблона. Как это:
function fmt([fisrt, ...rest], ...tags) {
return values => rest.reduce((acc, curr, i) => {
return acc + values[tags[i]] + curr;
}, fisrt);
}
Затем вы создаете свои шаблоны и откладываете замены:
> fmt'Test with ${0}, ${1}, ${2} and ${0} again'(['A', 'B', 'C']);
// 'Test with A, B, C and A again'
> template = fmt'Test with ${'foo'}, ${'bar'}, ${'baz'} and ${'foo'} again'
> template({ foo:'FOO', bar:'BAR' })
// 'Test with FOO, BAR, undefined and FOO again'
Другой вариант, более близкий к тому, что вы написали, - вернуть объект, расширенный из строки, чтобы получить утку из коробки и уважать интерфейс. Расширение String.prototype
не будет работать, потому что вам потребуется закрытие тега шаблона, чтобы разрешить параметры позже.
class FormatString extends String {
// Some other custom extensions that don't need the template closure
}
function fmt([fisrt, ...rest], ...tags) {
const str = new FormatString(rest.reduce((acc, curr, i) => '${acc}\${${tags[i]}}${curr}', fisrt));
str.format = values => rest.reduce((acc, curr, i) => {
return acc + values[tags[i]] + curr;
}, fisrt);
return str;
}
Затем в call-сайте:
> console.log(fmt'Hello, ${0}. This is a ${1}.'.format(["world", "test"]));
// Hello, world. This is a test.
> template = fmt'Hello, ${'foo'}. This is a ${'bar'}.'
> console.log(template)
// { [String: 'Hello, ${foo}. This is a ${bar}.'] format: [Function] }
> console.log(template.format({ foo: true, bar: null }))
// Hello, true. This is a null.
Вы можете обратиться к дополнительной информации и приложениям в этом другом ответе.
Ответ 6
AFAIS, полезная функция "отложенное выполнение шаблонов строк" по-прежнему недоступна. Использование лямбды - это выразительное, читаемое и краткое решение, однако:
var greetingTmpl = (...p)=>'Hello, ${p[0]}. This is a ${p[1]}';
console.log( greetingTmpl("world","test") );
console.log( greetingTmpl("@CodingIntrigue","try") );
Ответ 7
Вы можете вставить именованные значения в строку, используя функцию ниже
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
// --- test ---
// parameters in object
let t1 = 'My name is ${name}, I am ${age}. My brother name is also ${name}.';
let r1 = inject(t1, {name: 'JOHN',age: 23} );
console.log("OBJECT:", r1);
// parameters in array
let t2 = "Values ${0} are in ${2} array with ${1} values of ${0}."
let r2 = inject(t2, {...['A,B,C', 666, 'BIG']} );
console.log("ARRAY :", r2);