Ciągi znaków w JavaScript były historycznie ograniczone i nie miały możliwości, których można by oczekiwać w przypadku języków takich jak Python czy Ruby. ES6 Template Strings (dostępne w Chrome 41 i nowszych) zasadniczo to zmieniają. Umożliwiają one definiowanie ciągów znaków za pomocą języków specyficznych dla domeny (DSL), co zapewnia:
- Interpolacja ciągów znaków
- Wyrażenia umieszczone
- Ciągi wielowierszowe bez obejść
- Formatowanie ciągów znaków
- Tagowanie ciągów znaków w celu bezpiecznego usuwania znaków specjalnych z kodu HTML, lokalizacji i innych działań.
Zamiast dodawać kolejną funkcję do ciągów znaków w ich obecnej postaci, ciągi szablonowe wprowadzają zupełnie inny sposób rozwiązywania tych problemów.
Składnia
Ciągi szablonu używają znaków ` (``), a nie cudzysłowów pojedynczych lub podwójnych, do których jesteśmy przyzwyczajeni w przypadku zwykłych ciągów. Ciąg znaków szablonu może wyglądać tak:
var greeting = `Yo World!`;
Do tej pory ciągi szablonowe nie dały nam niczego więcej niż zwykłe ciągi. Zmieńmy to.
Podstawianie ciągów znaków
Jedną z pierwszych prawdziwych zalet jest zastępowanie ciągów znaków. Podstawianie umożliwia nam użycie dowolnego prawidłowego wyrażenia JavaScript (w tym np. dodawania zmiennych), a w literałach szablonu wynik zostanie wygenerowany jako część tego samego ciągu znaków.
Ciągi szablonu mogą zawierać symbole zastępcze do zastępowania ciągów znaków za pomocą składni ${ }, jak pokazano poniżej:
// Simple string substitution
var name = "Brendan";
console.log(`Yo, ${name}!`);
// => "Yo, Brendan!"
Wszystkie podstawienia ciągów znaków w ciągach szablonu są wyrażeniami JavaScript, więc możemy podstawiać nie tylko nazwy zmiennych. Na przykład poniżej możemy użyć interpolacji wyrażeń, aby osadzić czytelne wierszowe wyrażenie matematyczne:
var a = 10;
var b = 10;
console.log(`JavaScript first appeared ${a+b} years ago. Wow!`);
//=> JavaScript first appeared 20 years ago. Wow!
console.log(`The number of JS MVC frameworks is ${2 * (a + b)} and not ${10 * (a + b)}.`);
//=> The number of JS frameworks is 40 and not 200.
Są one również bardzo przydatne w przypadku funkcji w wyrażeniach:
function fn() { return "I am a result. Rarr"; }
console.log(`foo ${fn()} bar`);
//=> foo I am a result. Rarr bar.
Symbol ${} działa prawidłowo z dowolnym rodzajem wyrażenia, w tym z wyrażeniami elementów i wywołaniami metod:
var user = {name: 'Caitlin Potter'};
console.log(`Thanks for getting this into V8, ${user.name.toUpperCase()}.`);
// => "Thanks for getting this into V8, CAITLIN POTTER";
// And another example
var thing = 'template strings';
console.log(`Say hello to ${thing}.`);
// => Say hello to template strings
Jeśli w ciągu znaków potrzebujesz znaków `, możesz zmienić ich znaczenie za pomocą znaku ukośnika lewego \ w ten sposób:
var greeting = `\`Yo\` World!`;
Ciągi znaków z linią łamaną
Wielowierszowe ciągi znaków w JavaScript od jakiegoś czasu wymagają obejść. Obecne rozwiązania wymagają, aby ciągi znaków znajdowały się w jednym wierszu lub były dzielone na ciągi wielowierszowe za pomocą znaku \ (ukośnika) przed każdym znakiem nowego wiersza. Na przykład:
var greeting = "Yo \
World";
Chociaż w większości nowoczesnych silników JavaScriptu powinno to działać prawidłowo, samo zachowanie jest nadal nieco niekonwencjonalne. Można też użyć konkatenacji ciągów znaków, aby symulować obsługę wielu wierszy, ale to również pozostawia wiele do życzenia:
var greeting = "Yo " +
"World";
Ciągi szablonu znacznie upraszczają ciągi wielowierszowe. Wystarczy dodać znaki nowego wiersza w odpowiednich miejscach i gotowe. Oto przykład:
Wszystkie białe znaki w składni z odwrotnym apostrofem będą również traktowane jako część ciągu.
console.log(`string text line 1
string text line 2`);
Otagowane szablony
Do tej pory omawialiśmy używanie literałów szablonowych do zastępowania ciągów znaków i tworzenia ciągów wielowierszowych. Kolejną przydatną funkcją są szablony z tagami. Szablony otagowane przekształcają ciąg znaków szablonu, umieszczając nazwę funkcji przed ciągiem znaków szablonu. Na przykład:
fn`Hello ${you}! You're looking ${adjective} today!`
Semantyka oznaczonego ciągu szablonu bardzo różni się od semantyki zwykłego ciągu. Są one w zasadzie specjalnym rodzajem wywołania funkcji: powyższy kod „desugars” do
fn(["Hello ", "! You're looking ", " today!"], you, adjective);
Zwróć uwagę, że argument n+1 odpowiada podstawieniu, które następuje między n-tym a (n+1)-szym elementem w tablicy tekstowej. Może to być przydatne w wielu sytuacjach, ale jedną z najprostszych jest automatyczne usuwanie znaków specjalnych z interpolowanych zmiennych.
Możesz na przykład napisać funkcję, która będzie zamieniać znaki specjalne w HTML-u, tak aby…
html`<p title="${title}">Hello ${you}!</p>`
zwraca ciąg znaków z odpowiednimi zmiennymi, ale ze wszystkimi znakami niebezpiecznymi dla HTML zastąpionymi innymi znakami. Zróbmy to. Nasza funkcja ucieczki HTML będzie przyjmować 2 argumenty: nazwę użytkownika i komentarz. Oba mogą zawierać znaki niebezpieczne w HTML-u (czyli ', ", <, > i &). Jeśli na przykład nazwa użytkownika to „Domenic Denicola”, a komentarz to „& is a fun tag”, powinniśmy uzyskać te dane wyjściowe:
<b>Domenic Denicola says:</b> "& is a fun tag"
Rozwiązanie z otagowanym szablonem można zatem zapisać w ten sposób:
// HTML Escape helper utility
var util = (function () {
// Thanks to Andrea Giammarchi
var
reEscape = /[&<>'"]/g,
reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
oEscape = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
},
oUnescape = {
'&': '&',
'&': '&',
'<': '<',
'<': '<',
'>': '>',
'>': '>',
''': "'",
''': "'",
'"': '"',
'"': '"'
},
fnEscape = function (m) {
return oEscape[m];
},
fnUnescape = function (m) {
return oUnescape[m];
},
replace = String.prototype.replace
;
return (Object.freeze || Object)({
escape: function escape(s) {
return replace.call(s, reEscape, fnEscape);
},
unescape: function unescape(s) {
return replace.call(s, reUnescape, fnUnescape);
}
});
}());
// Tagged template function
function html(pieces) {
var result = pieces[0];
var substitutions = [].slice.call(arguments, 1);
for (var i = 0; i < substitutions.length; ++i) {
result += util.escape(substitutions[i]) + pieces[i + 1];
}
return result;
}
var username = "Domenic Denicola";
var tag = "& is a fun tag";
console.log(html`<b>${username} says</b>: "${tag}"`);
//=> <b>Domenic Denicola says</b>: "& is a fun tag"
Inne możliwe zastosowania to automatyczne usuwanie znaków specjalnych, formatowanie, lokalizacja i ogólnie bardziej złożone zamiany:
// Contextual auto-escaping
qsa`.${className}`;
safehtml`<a href="${url}?q=${query}" onclick="alert('${message}')" style="color: ${color}">${message}</a>`;
// Localization and formatting
l10n`Hello ${name}; you are visitor number ${visitor}:n! You have ${money}:c in your account!`
// Embedded HTML/XML
jsx`<a href="${url}">${text}</a>` // becomes React.DOM.a({ href: url }, text)
// DSLs for code execution
var childProcess = sh`ps ax | grep ${pid}`;
Podsumowanie
Ciągi szablonowe są dostępne w Chrome w wersji beta 41 lub nowszej, IE Tech Preview, Firefoksie 35 lub nowszym oraz io.js. Jeśli chcesz ich używać w środowisku produkcyjnym, są one obsługiwane w głównych transpilatorach ES6, w tym Traceur i 6to5. Jeśli chcesz je wypróbować, zapoznaj się z naszym przykładem ciągów szablonu w repozytorium przykładów Chrome. Może Cię też zainteresować artykuł ES6 Equivalents in ES5 (Odpowiedniki ES6 w ES5), w którym pokazujemy, jak za pomocą ES5 uzyskać niektóre z ułatwień, jakie dają ciągi szablonowe.
Ciągi szablonowe wprowadzają do JavaScriptu wiele ważnych funkcji. Obejmują one lepsze sposoby interpolacji ciągów znaków i wyrażeń, ciągi znaków wielowierszowych oraz możliwość tworzenia własnych języków DSL.
Jedną z najważniejszych funkcji, które wprowadzają, są szablony z tagami – kluczowa funkcja do tworzenia takich języków DSL. Otrzymują one części ciągu szablonu jako argumenty, a Ty możesz zdecydować, jak użyć ciągów i podstawień, aby określić ostateczny wynik ciągu.