A more advanced form of template literals are tagged templates.
Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions.
The tag function can then perform whatever operations on these arguments you wish, and return the manipulated string. (Alternatively, it can return something completely different, as described in one of the following examples.)
The name of the function used for the tag can be whatever you want.
const person = 'Mike';
const age = 28;
function myTag(strings, personExp, ageExp) {
const str0 = strings[0];
const str1 = strings[1];
const str2 = strings[2];
const ageStr = ageExp > 99 ? 'centenarian' : 'youngster';
return `${str0}${personExp}${str1}${ageStr}${str2}`;
}
const output = myTag`That ${person} is a ${age}.`;
console.log(output);
Tag functions don't even need to return a string!
function template(strings, ...keys) {
return (...values) => {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach((key, i) => {
const value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
const t1Closure = template`${0}${1}${0}!`;
t1Closure('Y', 'A');
const t2Closure = template`${0}${'foo'}!`;
t2Closure('Hello', { foo: 'World' });
const t3Closure = template`I'm ${'name'}. I'm almost ${'age'} years old.`;
t3Closure('foo', { name: 'MDN', age: 30 });
t3Closure({ name: 'MDN', age: 30 });
The first argument received by the tag function is an array of strings. For any template literal, its length is equal to the number of substitutions (occurrences of ${…}
) plus one, and is therefore always non-empty.
For any particular tagged template literal expression, the tag function will always be called with the exact same literal array, no matter how many times the literal is evaluated.
const callHistory = [];
function tag(strings, ...values) {
callHistory.push(strings);
return {};
}
function evaluateLiteral() {
return tag`Hello, ${'world'}!`;
}
console.log(evaluateLiteral() === evaluateLiteral());
console.log(callHistory[0] === callHistory[1]);
This allows the tag to cache the result based on the identity of its first argument. To further ensure the array value's stability, the first argument and its raw
property are both frozen, so you can't mutate them in any way.