Template Literals
At the very outset of this section, I’m going to have to call out the name of this ES6 feature as being awfully… misleading, depending on your experiences with what the word template means.
Many developers think of templates as being reusable renderable pieces of text, such as the capability provided by most template engines (Mustache, Handlebars, etc.). ES6’s use of the word template would imply something similar, like a way to declare inline template literals that can be re-rendered. However, that’s not at all the right way to think about this feature.
So, before we go on, I’m renaming to what it should have been called: interpolated string literals (or interpoliterals for short).
You’re already well aware of declaring string literals with "
or '
delimiters, and you also know that these are not smart strings (as some languages have), where the contents would be parsed for interpolation expressions.
However, ES6 introduces a new type of string literal, using the `
backtick as the delimiter. These string literals allow basic string interpolation expressions to be embedded, which are then automatically parsed and evaluated.
Here’s the old pre-ES6 way:
var name = "Kyle";
var greeting = "Hello " + name + "!";
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
Now, consider the new ES6 way:
var name = "Kyle";
var greeting = `Hello ${name}!`;
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
As you can see, we used the `..`
around a series of characters, which are interpreted as a string literal, but any expressions of the form ${..}
are parsed and evaluated inline immediately. The fancy term for such parsing and evaluating is interpolation (much more accurate than templating).
The result of the interpolated string literal expression is just a plain old normal string, assigned to the greeting
variable.
Warning: typeof greeting == "string"
illustrates why it’s important not to think of these entities as special template values, as you cannot assign the unevaluated form of the literal to something and reuse it. The `..`
string literal is more like an IIFE in the sense that it’s automatically evaluated inline. The result of a `..`
string literal is, simply, just a string.
One really nice benefit of interpolated string literals is they are allowed to split across multiple lines:
var text =
`Now is the time for all good men
to come to the aid of their
country!`;
console.log( text );
// Now is the time for all good men
// to come to the aid of their
// country!
The line breaks (newlines) in the interpolated string literal were preserved in the string value.
Unless appearing as explicit escape sequences in the literal value, the value of the \r
carriage return character (code point U+000D
) or the value of the \r\n
carriage return + line feed sequence (code points U+000D
and U+000A
) are both normalized to a \n
line feed character (code point U+000A
). Don’t worry though; this normalization is rare and would likely only happen if copy-pasting text into your JS file.
Interpolated Expressions
Any valid expression is allowed to appear inside ${..}
in an interpolated string literal, including function calls, inline function expression calls, and even other interpolated string literals!
Consider:
function upper(s) {
return s.toUpperCase();
}
var who = "reader";
var text =
`A very ${upper( "warm" )} welcome
to all of you ${upper( `${who}s` )}!`;
console.log( text );
// A very WARM welcome
// to all of you READERS!
Here, the inner `${who}s`
interpolated string literal was a little bit nicer convenience for us when combining the who
variable with the "s"
string, as opposed to who + "s"
. There will be cases that nesting interpolated string literals is helpful, but be wary if you find yourself doing that kind of thing often, or if you find yourself nesting several levels deep.
If that’s the case, the odds are good that your string value production could benefit from some abstractions.
Warning: As a word of caution, be very careful about the readability of your code with such new found power. Just like with default value expressions and destructuring assignment expressions, just because you can do something doesn’t mean you should do it. Never go so overboard with new ES6 tricks that your code becomes more clever than you or your other team members.
Expression Scope
One quick note about the scope that is used to resolve variables in expressions. I mentioned earlier that an interpolated string literal is kind of like an IIFE, and it turns out thinking about it like that explains the scoping behavior as well.
Consider:
function foo(str) {
var name = "foo";
console.log( str );
}
function bar() {
var name = "bar";
foo( `Hello from ${name}!` );
}
var name = "global";
bar(); // "Hello from bar!"
At the moment the `..`
string literal is expressed, inside the bar()
function, the scope available to it finds bar()
‘s name
variable with value "bar"
. Neither the global name
nor foo(..)
‘s name
matter. In other words, an interpolated string literal is just lexically scoped where it appears, not dynamically scoped in any way.
Tagged Template Literals
Again, renaming the feature for sanity sake: tagged string literals.
To be honest, this is one of the cooler tricks that ES6 offers. It may seem a little strange, and perhaps not all that generally practical at first. But once you’ve spent some time with it, tagged string literals may just surprise you in their usefulness.
For example:
function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
var desc = "awesome";
foo`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]
Let’s take a moment to consider what’s happening in the previous snippet. First, the most jarring thing that jumps out is foo`Everything...`;
. That doesn’t look like anything we’ve seen before. What is it?
It’s essentially a special kind of function call that doesn’t need the ( .. )
. The tag — the foo
part before the `..`
string literal — is a function value that should be called. Actually, it can be any expression that results in a function, even a function call that returns another function, like:
function bar() {
return function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
}
var desc = "awesome";
bar()`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]
But what gets passed to the foo(..)
function when invoked as a tag for a string literal?
The first argument — we called it strings
— is an array of all the plain strings (the stuff between any interpolated expressions). We get two values in the strings
array: "Everything is "
and "!"
.
For convenience sake in our example, we then gather up all subsequent arguments into an array called values
using the ...
gather/rest operator (see the “Spread/Rest” section earlier in this chapter), though you could of course have left them as individual named parameters following the strings
parameter.
The argument(s) gathered into our values
array are the results of the already-evaluated interpolation expressions found in the string literal. So obviously the only element in values
in our example is "awesome"
.
You can think of these two arrays as: the values in values
are the separators if you were to splice them in between the values in strings
, and then if you joined everything together, you’d get the complete interpolated string value.
A tagged string literal is like a processing step after the interpolation expressions are evaluated but before the final string value is compiled, allowing you more control over generating the string from the literal.
Typically, the string literal tag function (foo(..)
in the previous snippets) should compute an appropriate string value and return it, so that you can use the tagged string literal as a value just like untagged string literals:
function tag(strings, ...values) {
return strings.reduce( function(s,v,idx){
return s + (idx > 0 ? values[idx-1] : "") + v;
}, "" );
}
var desc = "awesome";
var text = tag`Everything is ${desc}!`;
console.log( text ); // Everything is awesome!
In this snippet, tag(..)
is a pass-through operation, in that it doesn’t perform any special modifications, but just uses reduce(..)
to loop over and splice/interleave strings
and values
together the same way an untagged string literal would have done.
So what are some practical uses? There are many advanced ones that are beyond our scope to discuss here. But here’s a simple idea that formats numbers as U.S. dollars (sort of like basic localization):
function dollabillsyall(strings, ...values) {
return strings.reduce( function(s,v,idx){
if (idx > 0) {
if (typeof values[idx-1] == "number") {
// look, also using interpolated
// string literals!
s += `$${values[idx-1].toFixed( 2 )}`;
}
else {
s += values[idx-1];
}
}
return s + v;
}, "" );
}
var amt1 = 11.99,
amt2 = amt1 * 1.08,
name = "Kyle";
var text = dollabillsyall
`Thanks for your purchase, ${name}! Your
product cost was ${amt1}, which with tax
comes out to ${amt2}.`
console.log( text );
// Thanks for your purchase, Kyle! Your
// product cost was $11.99, which with tax
// comes out to $12.95.
If a number
value is encountered in the values
array, we put "$"
in front of it and format it to two decimal places with toFixed(2)
. Otherwise, we let the value pass-through untouched.
Raw Strings
In the previous snippets, our tag functions receive the first argument we called strings
, which is an array. But there’s an additional bit of data included: the raw unprocessed versions of all the strings. You can access those raw string values using the .raw
property, like this:
function showraw(strings, ...values) {
console.log( strings );
console.log( strings.raw );
}
showraw`Hello\nWorld`;
// [ "Hello
// World" ]
// [ "Hello\nWorld" ]
The raw version of the value preserves the raw escaped \n
sequence (the \
and the n
are separate characters), while the processed version considers it a single newline character. However, the earlier mentioned line-ending normalization is applied to both values.
ES6 comes with a built-in function that can be used as a string literal tag: String.raw(..)
. It simply passes through the raw versions of the strings
values:
console.log( `Hello\nWorld` );
// Hello
// World
console.log( String.raw`Hello\nWorld` );
// Hello\nWorld
String.raw`Hello\nWorld`.length;
// 12
Other uses for string literal tags included special processing for internationalization, localization, and more!