Interpolacja stringów

Do tej pory poznaliśmy dwa sposoby zapisu stringów:


const var1 = 1;
const var2 = 2;

const text1 = "Ala ma " + var1 + " kota i " + var2 + " psy.";
const text2 = 'Ala ma ' + var1 + ' kota i ' + var2 + ' psy.';

Podstawowy problem z tego typu zapisem jest taki, że ciężko się wstawia do nich zmienne, a i tworzenie wieloliniowych ciągów do najprzyjemniejszych nie należy:


const html  = "<div class=\"module\">";
      html += "    <div class="module-header">";
      html += "        <h2 class="module-title">" + title + "</h2>";
      html += "    </div>";
      html += "    <div class="module-content">";
      html += "         <div>" + content + "</div>";
      html += "    </div>";
      html += "</div>";

Możemy posiłkować się pojedynczymi apostrofami i znakiem nowej linii:


const html  = '\
<div class=\"module\"> \
   <div class="module-header"> \
       <h2 class="module-title">' + title + '</h2> \
   </div> \
   <div class="module-content"> \
        <div>' + content + '</div> \
   </div> \
</div>';

...ale wciąż nie jest to najwygodniejsze rozwiązanie.

Stringi interpolowane

Ecmascript 2015 wprowadza nowy sposób tworzenia stringów za pomocą tak zwanych backtick (na klawiaturach znaczek tuż nad tyldą):


const var1 = 1;
const var2 = 2;

const text = `Ala ma ${var1} kota i ${var2} psy.`;

Bezpośrednio do takiego tekstu możemy wstawiać kod JS, w tym wywoływanie funkcji, zmienne itp. Jak widzisz powyżej robi się to za pomocą znaku dolara i klamer ${....}, między które trafia kod JS:


const dayData = {
    temp : 23,
    weather : 'pogodna'
}

function calculateWeather() {
    return "bardzo ładna :)"
}

const text = `
    Dzisiaj jest ${ (new Date()).getFullYear() }.
    Na dworze jest temperatura ${ dayData.temp }
    A pogoda jest ${ calculateWeather() }
`;

Ta metoda tworzenia ciągów znaków idealnie się nadaje do tworzenia kawałków kodu html. To z czym się wcześniej męczyliśmy, teraz wygląda o wiele lepiej:


const text = `
    <div class="module">
        <div class="module-header">
            <h2 class="module-title"> ${title} </h2>
        </div>
        <div class="module-content">
            <div>${content}</div>
        </div>
    </div>`;
Gdy piszę te słowa niektóre leciwe przeglądarki jeszcze nie obsługują powyższego sposobu deklaracji stringów. Zapewne za chwilę nie będzie to miało znaczenia, ale warte jest zaznaczenia.

Dodatkowe metody dla stringów

Poza powyższym ułatwieniem ES6 wprowadza także nowe metody do pracy ze stringami:

Nazwa metody Co robi
startsWith(str) sprawdza czy dany tekst zaczyna się od str
endsWith(str) sprawdza czy dany tekst kończy się str
includes(str) sprawdza czy dany tekst zawiera w sobie str
repeat(x) zwraca nowy string będący powtórzeniem x razy danego stringu

const text = "Bardzo lubię czerwone jagódki";

text.startsWith("Bardzo"); //true
text.startsWith("lubię"); //false

text.endsWith("jagódki"); //true
text.endsWith("czerwone"); //false

text.includes("Bardzo"); //true
text.includes("jagódki"); //true
text.includes("czerwone"); //true
text.includes("pietruszkę"); //false

console.log("siano".repeat(3)); //sianosianosiano
console.log("G" + "o".repeat(10) + "l!"); //Gooooooooool!

No dobrze ale przecież mamy indexOf, a ostatnie linijki też spokojnie moglibyśmy stworzyć.
Tak - ale znowu - ES6 nie stara się odkrywać koła na nowo, a tylko ułatwić nam życie:


const text = "Bardzo lubię czerwone jagódki";

text.startsWith("Bardzo");
text.indexOf("Bardzo") === 0

text.endsWith("jagódki");
text.indexOf('jagódki') === text.length - "jagódki".length
(new RegExp("jagódki$")).test(text)

text.includes("Bardzo");
text.indexOf("Bardzo") !== -1

console.log("siano".repeat(3));
Array(3).join("siano");

Funkcje tagujące

ES6 wprowadza dla template stringów jeszcze jedną nową funkcjonalność - tak zwane funkcje tagujące. Są to funkcje, które przekształcają template string.


function formatString(stringsParts, var1, var2) {
    console.log(stringsParts); // ["Cena produktu A to ", " i jest o ", " tańsza od produktu B"]
    console.log(var1); //2000
    console.log(var2); //150
}

const price = 2000;
const diff = 150;

const text = formatString`Cena produktu A to ${price} i jest o ${diff} tańsza od produktu B`;

Funkcje tagujące umieszczamy bezpośrednio przed template stringiem. Do funkcji takiej zostaną przekazane w pierwszym parametrze wszystkie części template stringa między zmiennymi. Do kolejnych parametrów zostaną przekazane kolejne zmienne z klamer.

Najczęściej nie będziemy wiedzieli ile jest takich zmiennych, dlatego najlepszym rozwiązaniem jest tutaj zastosować operator rest:


function format(parts, ...vars) {
    console.log(parts); //["Nazywam się ", " i mam ", " lat"] /dlugosc 3
    console.log(vars); //["Piotr", 16] //długość zawsze o 1 mniejsza niż parts
}

const name = "Piotr";
const age = 16;

const text = format`Nazywam się ${name} i mam ${age} lat`;

Główną zaletą funkcji tagujących jest to, że jeżeli zwrócą one jakąś wartość, zatąpi ona nasz template string:


function format(parts, ...vars) {
    return "Hej!";
}

const name = "Piotr";
const age = 16;

const text = format`Nazywam się ${name} i mam ${age} lat`;
console.log(text); //"Hej!"

Możemy to wykorzystać do modyfikacji naszych template stringów. Wystarczy zrobić pętlę po parts i vars łącząc odpowiednie części w jeden string. W poniższym przykładzie każdą wartość z klamer dodatkowo modyfikuję za pomocą meotdy toFixed:


function formatString(parts, ...vars) {
    let str = '';
    parts.forEach((el, i) => {
        str += el;
        if (vars[i] !== undefined) {
            str += vars[i].toFixed(2);
        }
    });
    return str;
}

const price = 2000;
const diff = 150;

const text = formatString`Cena produktu A to ${price} i jest o ${diff} tańsza od produktu B`;

console.log(text); //"Cena produktu A to 2000.00 i jest o 150.00 tańsza od produktu B"

Kolejny przykład pokazuje jak możemy wykorzystać funkcje tagujące do oblewania zmiennych tagami html:


function format(parts, ...vars) {
    let str = '';
    parts.forEach((el, i) => {
        str += el;
        if (vars[i] !== undefined) {
            str += '<strong>' + vars[i].toUpperCase() + '</strong>';
        }
    });
    return str;
}

const name = "marcin";
const dog = "szamson";

const text = format`Mam na imię ${name} a mój pies to ${dog}`;

console.log(text); //"Mam na imię <strong>MARCIN</strong> a mój pies to <strong>SZAMSON</strong>"

Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Stwórz obiekt myPerson który ma:
    - właściwość name
    - właściwość height
    - metodę calculateAge(), która wyliczy twój wiek wyliczając go na bazie obecnego roku (użyj odpowiedniej metody obiektu Date) (zrób to z przybliżeniem, nie ważny jest dokładny wiek)
  2. Wypisz w konsoli tekst:
    
                Nazywam się ....
                Mój wzrost to ...
                Mam ... lat
            
    Podstaw w odpowiednie miejsca odpowiednie składowe obiektu z zadania 1
    
                const myPerson = {
                    name : "Marcin",
                    height : 184,
                    calculateAge : function() {
                        return (new Date()).getFullYear() - 1981;
                    }
                }
    
                console.log(`
                    Nazywam się ${myPerson.name}
                    Mój wzrost to ${myPerson.height}
                    Mam ${myPerson.calculateAge()} lat
                `);
                
  3. Stwórz funkcję tagującą, która przekształci powyższy string tak, że każda wartość będzie obięta nawiasami klamrowymi np. "Nazywam się [Marcin]"
    
                function changeText(parts, ...vars) {
                    let str = '';
                    parts.forEach((el, i) => {
                        str += el;
                        if (vars[i] !== undefined) {
                            str += "[" + vars[i] + "]";
                        }
                    });
                    return str;
                }
    
                const myPerson = {
                    name : "Marcin",
                    height : 184,
                    calculateAge : function() {
                        return (new Date()).getFullYear() - 1981;
                    }
                }
    
                console.log(changeText`
                    Nazywam się ${myPerson.name}
                    Mój wzrost to ${myPerson.height}
                    Mam ${myPerson.calculateAge()} lat
                `);