In this article, we cover ways to join strings. Even though it includes old methods of concatenating strings, we focus on template literals. Here you can learn how they work and how to expand their functionality with tags. Let’s go!
Before template literals
Back in the days, we didn’t have template literals and to merge strings we used other techniques. Some of them were:
Addition operator
As mentioned in the Looking into assembly code of coercion, if any operands of the + operator is a string, the result will also be a string. Such operation converts the other operand to a string if needed.
1 2 3 |
const dogName = 'Fluffy'; console.log('My dog, ' + dogName + ', is a great pet!'); // My dog, Fluffy, is a great pet! |
Since the string is a primitive type, it is immutable. It means that it can’t be altered and when attempting to add one string to the other, a new one is created. This applies to all of the ways of concatenating strings.
1 2 |
let greetings = 'Hello'; greetings += ', have a great day!'; // a brand new string created here |
Trying to modify an existing string will not have any effect and throw an error in the strict mode!
1 |
greetings[0] = 'h'; |
Uncaught TypeError: Cannot assign to read only property ‘0’ of string ‘Hello’
Object descriptors ensure that – all characters in the string array are read-only.
1 |
Object.getOwnPropertyDescriptor(greetings, '0').writable // false |
Check out Object property descriptors, if you would like to know more about them
String.prototype.concat
The concat function joins together the calling string with provided arguments and returns a new string. An important thing is that it does not change the string, but returns a new one.
1 2 3 |
const hello = 'Hello'; console.log(hello.concat(' World!')); // Hello World! console.log(hello); // Hello |
According to the MDN, using the addition operator is much faster.
Template literals
The template literals, previously also called template strings, are a new way to join strings introduced in ES6. Such strings are enclosed by the back-tick – `` – the grave accent. Template literals can evaluate expressions inserted as a part of the string, enclosed in a dollar sign and curly brackets.
1 2 3 |
const dogName = 'Fluffy'; console.log(`My dog, ${dogName}, is a great pet!`); // My dog, Fluffy, is a great pet! |
It looks clean and straightforward, especially combined with the usage of good variable names.
If you ever need to put a back-tick into the string, you need to escape it: to do this, put a backslash before the backtick.
1 |
console.log(`Back-tick: ``); // Back-tick: ` |
A noteworthy aspect of using template literals are multi-line strings. If you press enter in the middle of a template literal, you insert a newline character.
1 2 3 4 5 6 |
const pets = `Dog: Fluffy Cat: Garfield`; console.log(pets); // Dog: Fluffy // Cat: Garfield |
To achieve the same output without template strings, you need to insert newline characters manually:
1 2 3 4 5 |
const pets = 'Dog: Fluffy\nCat: Garfield'; console.log(pets); // Dog: Fluffy // Cat: Garfield |
Hitting enter in a regular string causes Uncaught SyntaxError: Invalid or unexpected token and to prevent it, you need to escape newlines.
1 2 |
const pets = 'Dog: Fluffy\ Cat: Garfield'; |
This does not insert newline characters though and you still need to add it manually.
Tagged templates
When you use regular template literals, your input is passed to a default function that concatenates it into a single string. An interesting thing is that you can change it by preceding the template literal with your function name that acts as a tag. By doing it, you create a tagged template.
Using tags is a more advanced way to put the template literals to work: with them, you can manipulate your strings before outputting them.
The first argument of the tag function is an array of string values from your literal. The rest of the arguments are the expressions. The return of the tag function is the output of the tagged template literal.
1 2 3 4 5 6 7 8 9 10 |
const dog = { name: 'Fluffy' } function petTag(strings, pet) { console.log(strings); // ['My dog, ', ', is a great pet!']; return `${strings[0]}${pet.name}${strings[1]}`; } console.log(petTag`My dog, ${dog}, is a great pet!`); // My dog, Fluffy, is a great pet! |
Imagine wanting to bold every expression that is a part of your template literal when using markdown. To bold something in markdown, you need to enclose it in asterisks.
1 2 3 4 5 6 7 8 9 10 11 |
function bold(strings, ...expressions) { return strings.reduce((result, currentString, i) => ( `${result}${currentString}${expressions[i] ? `*${expressions[i]}*` : ''}` ), '') } const dogName = 'Fluffy'; const age = 3; console.log(bold`The name of my dog is ${dogName}. He is ${age} years old.`); // The name of my dog is *Fluffy*. He is *3* years old |
A common use-case is in a popular library styled-components. It is a good example that tag functions don’t need to return a string:
1 2 3 4 5 6 7 |
import styled from 'styled-components'; const Element = styled.div` background: url(${props => props.imageUrl}) ` console.log(typeof Element); // object |
In this case the styled.div returns an object and your tag functions can also do that!
Summary
In this article, we covered string concatenation. It included going through the old ways of joining strings like the addition operator and functions like String.prototype.concat. We covered template strings, a new way of concatenating strings that came with the ES6. We learned about how they work and how to enhance their functionalities with tags.