8. Template-Literale
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

8. Template-Literale



8.1 Übersicht

ES6 hat zwei neue Arten von Literalen: *Template-Literale* und *Tagged Template-Literale*. Diese beiden Literale haben ähnliche Namen und sehen ähnlich aus, sind aber recht unterschiedlich. Es ist daher wichtig, zu unterscheiden

*Template-Literale* sind String-Literale, die sich über mehrere Zeilen erstrecken und interpolierte Ausdrücke (eingefügt über ${···}) enthalten können.

const firstName = 'Jane';
console.log(`Hello ${firstName}!
How are you
today?`);

// Output:
// Hello Jane!
// How are you
// today?

*Tagged Template-Literale* (kurz: *Tagged Templates*) werden erstellt, indem eine Funktion vor einem Template-Literal genannt wird.

> String.raw`A \tagged\ template`
'A \\tagged\\ template'

Tagged Templates sind Funktionsaufrufe. Im vorherigen Beispiel wird die Methode String.raw aufgerufen, um das Ergebnis des Tagged Templates zu erzeugen.

8.2 Einführung

Literale sind syntaktische Konstrukte, die Werte erzeugen. Beispiele hierfür sind String-Literale (die Strings erzeugen) und reguläre Ausdrucks-Literale (die reguläre Ausdrucksobjekte erzeugen). ECMAScript 6 hat zwei neue Literale

Es ist wichtig zu bedenken, dass die Namen von Template-Literale und Tagged Templates leicht irreführend sind. Sie haben nichts mit *Templates* zu tun, wie sie oft in der Webentwicklung verwendet werden: Textdateien mit Platzhaltern, die über (z. B.) JSON-Daten gefüllt werden können.

8.2.1 Template-Literale

Ein Template-Literal ist eine neue Art von String-Literal, die sich über mehrere Zeilen erstrecken und Ausdrücke *interpolieren* (deren Ergebnisse einschließen) kann. Zum Beispiel

const firstName = 'Jane';
console.log(`Hello ${firstName}!
How are you
today?`);

// Output:
// Hello Jane!
// How are you
// today?

Das Literal selbst wird durch Backticks (`) begrenzt, die interpolierten Ausdrücke innerhalb des Literals werden durch ${ und } begrenzt. Template-Literale erzeugen immer Strings.

8.2.2 Escaping in Template-Literale

Der Backslash wird zum Escaping innerhalb von Template-Literale verwendet.

Er ermöglicht es Ihnen, Backticks und ${ in Template-Literale einzufügen

> `\``
'`'
> `$` // OK
'$'
> `${`
SyntaxError
> `\${`
'${'
> `\${}`
'${}'

Ansonsten funktioniert der Backslash wie in String-Literale

> `\\`
'\\'
> `\n`
'\n'
> `\u{58}`
'X'

8.2.3 Zeilenumbrüche in Template-Literale sind immer LF (\n)

Gängige Methoden zur Beendigung von Zeilen sind

Alle diese Zeilenumbrüche werden in Template-Literale auf LF normalisiert. Das heißt, der folgende Code gibt auf allen Plattformen true aus

const str = `BEFORE
AFTER`;
console.log(str === 'BEFORE\nAFTER'); // true

8.2.4 Tagged Template-Literale

Das Folgende ist ein *Tagged Template-Literal* (kurz: *Tagged Template*)

tagFunction`Hello ${firstName} ${lastName}!`

Das Setzen eines Template-Literals nach einem Ausdruck löst einen Funktionsaufruf aus, ähnlich wie eine Parameterliste (kommagetrennte Werte in Klammern) einen Funktionsaufruf auslöst. Der vorherige Code ist äquivalent zum folgenden Funktionsaufruf (in Wirklichkeit ist der erste Parameter mehr als nur ein Array, aber das wird später erklärt).

tagFunction(['Hello ', ' ', '!'], firstName, lastName)

Somit ist der Name vor dem Inhalt in Backticks der Name einer Funktion, die aufgerufen werden soll, die *Tag-Funktion*. Die Tag-Funktion erhält zwei verschiedene Arten von Daten

Template-Strings sind statisch bekannt (zur Kompilierzeit), Substitutionen sind nur zur Laufzeit bekannt. Die Tag-Funktion kann mit ihren Parametern beliebig verfahren: Sie kann die Template-Strings vollständig ignorieren, Werte beliebigen Typs zurückgeben usw.

Zusätzlich erhalten Tag-Funktionen zwei Versionen jedes Template-Strings

Das ermöglicht String.raw (das später erklärt wird), seine Arbeit zu tun.

> String.raw`\n` === '\\n'
true

8.3 Beispiele für die Verwendung von Tagged Template-Literale

Tagged Template-Literale ermöglichen es Ihnen, benutzerdefinierte eingebettete Subsprachen (die manchmal als *domänenspezifische Sprachen* bezeichnet werden) mit wenig Aufwand zu implementieren, da JavaScript viel vom Parsen für Sie übernimmt. Sie müssen nur eine Funktion schreiben, die die Ergebnisse erhält.

Schauen wir uns Beispiele an. Einige von ihnen sind inspiriert von dem ursprünglichen Vorschlag für Template-Literale, der sie unter ihrem alten Namen *Quasi-Literale* erwähnt.

8.3.1 Rohe Strings

ES6 enthält die Tag-Funktion String.raw für *rohe Strings*, bei denen Backslashes keine spezielle Bedeutung haben.

const str = String.raw`This is a text
with multiple lines.
Escapes are not interpreted,
\n is not a newline.`;

Dies ist nützlich, wann immer Sie Strings erstellen müssen, die Backslashes enthalten. Zum Beispiel

function createNumberRegExp(english) {
    const PERIOD = english ? String.raw`\.` : ','; // (A)
    return new RegExp(`[0-9]+(${PERIOD}[0-9]+)?`);
}

In Zeile A ermöglicht uns String.raw, den Backslash so zu schreiben, wie wir es in einem regulären Ausdrucks-Literal tun würden. Bei normalen String-Literalen müssen wir doppelt escapen: Zuerst müssen wir den Punkt für den regulären Ausdruck escapen. Zweitens müssen wir den Backslash für das String-Literal escapen.

8.3.2 Shell-Befehle

const proc = sh`ps ax | grep ${pid}`;

(Quelle: David Herman)

8.3.3 Byte-Strings

const buffer = bytes`455336465457210a`;

(Quelle: David Herman)

8.3.4 HTTP-Anfragen

POST`http://foo.org/bar?a=${a}&b=${b}
     Content-Type: application/json
     X-Credentials: ${credentials}

     { "foo": ${foo},
       "bar": ${bar}}
     `
     (myOnReadyStateChangeHandler);

(Quelle: Luke Hoban)

8.3.5 Leistungsfähigere reguläre Ausdrücke

Steven Levithan hat ein Beispiel dafür gegeben, wie Tagged Template-Literale für seine Bibliothek für reguläre Ausdrücke XRegExp verwendet werden könnten.

Ohne Tagged Templates schreiben Sie Code wie den folgenden

var parts = '/2015/10/Page.html'.match(XRegExp(
  '^ # match at start of string only \n' +
  '/ (?<year> [^/]+ ) # capture top dir name as year \n' +
  '/ (?<month> [^/]+ ) # capture subdir name as month \n' +
  '/ (?<title> [^/]+ ) # capture base name as title \n' +
  '\\.html? $ # .htm or .html file ext at end of path ', 'x'
));

console.log(parts.year); // 2015

Wir sehen, dass XRegExp uns benannte Gruppen (year, month, title) und das x-Flag gibt. Mit diesem Flag wird der meiste Leerraum ignoriert und Kommentare können eingefügt werden.

Es gibt zwei Gründe, warum String-Literale hier nicht gut funktionieren. Erstens müssen wir jeden Backslash eines regulären Ausdrucks zweimal tippen, um ihn für das String-Literal zu escapen. Zweitens ist es umständlich, mehrere Zeilen einzugeben.

Anstatt Strings zu verketten, können Sie auch einen String-Literal in der nächsten Zeile fortsetzen, wenn Sie die aktuelle Zeile mit einem Backslash beenden. Aber das ist immer noch mit viel visueller Unordnung verbunden, besonders weil Sie immer noch den expliziten Zeilenumbruch über \n am Ende jeder Zeile benötigen.

var parts = '/2015/10/Page.html'.match(XRegExp(
  '^ # match at start of string only \n\
  / (?<year> [^/]+ ) # capture top dir name as year \n\
  / (?<month> [^/]+ ) # capture subdir name as month \n\
  / (?<title> [^/]+ ) # capture base name as title \n\
  \\.html? $ # .htm or .html file ext at end of path ', 'x'
));

Probleme mit Backslashes und mehreren Zeilen verschwinden mit Tagged Templates.

var parts = '/2015/10/Page.html'.match(XRegExp.rx`
    ^ # match at start of string only
    / (?<year> [^/]+ ) # capture top dir name as year
    / (?<month> [^/]+ ) # capture subdir name as month
    / (?<title> [^/]+ ) # capture base name as title
    \.html? $ # .htm or .html file ext at end of path
`);

Zusätzlich erlauben Tagged Templates, Werte v über ${v} einzufügen. Ich würde erwarten, dass eine Bibliothek für reguläre Ausdrücke Strings escapet und reguläre Ausdrücke unverändert einfügt. Zum Beispiel

var str   = 'really?';
var regex = XRegExp.rx`(${str})*`;

Dies wäre äquivalent zu

var regex = XRegExp.rx`(really\?)*`;

8.3.6 Abfragesprachen

Beispiel

$`a.${className}[href*='//${domain}/']`

Dies ist eine DOM-Abfrage, die nach allen <a>-Tags sucht, deren CSS-Klasse className ist und deren Ziel eine URL mit der gegebenen Domain ist. Die Tag-Funktion $ stellt sicher, dass die Argumente korrekt escaped werden, was diesen Ansatz sicherer macht als manuelle String-Verkettung.

8.3.7 React JSX über Tagged Templates

Facebook React ist „eine JavaScript-Bibliothek zum Erstellen von Benutzeroberflächen“. Es hat die optionale Spracherweiterung JSX, die es Ihnen ermöglicht, *virtuelle* DOM-Bäume für Benutzeroberflächen zu erstellen. Diese Erweiterung macht Ihren Code prägnanter, ist aber auch nicht standardkonform und bricht die Kompatibilität mit dem Rest des JavaScript-Ökosystems.

Die Bibliothek t7.js bietet eine Alternative zu JSX und verwendet Templates, die mit t7 getaggt sind.

t7.module(function(t7) {
  function MyWidget(props) {
    return t7`
      <div>
        <span>I'm a widget ${ props.welcome }</span>
      </div>
    `;
  }

  t7.assign('Widget', MyWidget);

  t7`
    <div>
      <header>
        <Widget welcome="Hello world" />
      </header>
    </div>
  `;
});

In „Why not Template Literals?“ erklärt das React-Team, warum sie sich entschieden haben, keine Template-Literale zu verwenden. Eine Herausforderung ist der Zugriff auf Komponenten innerhalb von Tagged Templates. Zum Beispiel wird MyWidget aus dem zweiten Tagged Template im vorherigen Beispiel zugegriffen. Eine umständliche Möglichkeit, dies zu tun, wäre

<${MyWidget} welcome="Hello world" />

Stattdessen verwendet t7.js eine Registry, die über t7.assign() gefüllt wird. Das erfordert zusätzliche Konfiguration, aber die Template-Literale sehen schöner aus; besonders wenn es sowohl einen öffnenden als auch einen schließenden Tag gibt.

8.3.8 Facebook GraphQL

Facebook Relay ist ein „JavaScript-Framework zum Erstellen datengesteuerter React-Anwendungen“. Ein Teil davon ist die Abfragesprache GraphQL, deren Abfragen über Templates erstellt werden können, die mit Relay.QL getaggt sind. Zum Beispiel ( entlehnt von der Relay-Homepage)

class Tea extends React.Component {
  render() {
    var {name, steepingTime} = this.props.tea;
    return (
      <li key={name}>
        {name} (<em>{steepingTime} min</em>)
      </li>
    );
  }
}
Tea = Relay.createContainer(Tea, {
  fragments: { // (A)
    tea: () => Relay.QL`
      fragment on Tea {
        name,
        steepingTime,
      }
    `,
  },
});

class TeaStore extends React.Component {
  render() {
    return <ul>
      {this.props.store.teas.map(
        tea => <Tea tea={tea} />
      )}
    </ul>;
  }
}
TeaStore = Relay.createContainer(TeaStore, {
  fragments: { // (B)
    store: () => Relay.QL`
      fragment on Store {
        teas { ${Tea.getFragment('tea')} },
      }
    `,
  },
});

Die Objekte, beginnend in Zeile A und Zeile B, definieren *Fragmente*, die über Callbacks definiert werden, die Abfragen zurückgeben. Das Ergebnis des Fragments tea wird in this.props.tea eingefügt. Das Ergebnis des Fragments store wird in this.props.store eingefügt.

Dies sind die Daten, auf denen die Abfragen operieren.

const STORE = {
  teas: [
    {name: 'Earl Grey Blue Star', steepingTime: 5},
    ···
  ],
};

Diese Daten sind in einer Instanz von GraphQLSchema verpackt, wo sie den Namen Store erhält (wie in fragment on Store erwähnt).

8.3.9 Textlokalisierung (L10N)

Dieser Abschnitt beschreibt einen einfachen Ansatz zur Textlokalisierung, der verschiedene Sprachen und verschiedene Locales unterstützt (wie Zahlen, Zeit usw. formatiert werden). Gegeben die folgende Nachricht.

alert(msg`Welcome to ${siteName}, you are visitor
          number ${visitorNumber}:d!`);

Die Tag-Funktion msg würde wie folgt funktionieren.

Erstens werden die Literal-Teile zu einem String verkettet, der verwendet werden kann, um eine Übersetzung in einer Tabelle nachzuschlagen. Der Lookup-String für das vorherige Beispiel ist

'Welcome to {0}, you are visitor number {1}!'

Dieser Lookup-String könnte zum Beispiel einer deutschen Übersetzung zugeordnet werden:

'Besucher Nr. {1}, willkommen bei {0}!'

Die englische „Übersetzung“ wäre dieselbe wie der Lookup-String.

Zweitens wird das Ergebnis aus dem Lookup verwendet, um die Substitutionen anzuzeigen. Da ein Lookup-Ergebnis Indizes enthält, kann es die Reihenfolge der Substitutionen ändern. Dies wurde im Deutschen getan, wo die Besuchernummer vor dem Seitennamen steht. Wie die Substitutionen formatiert werden, kann über Annotationen wie :d beeinflusst werden. Diese Annotation bedeutet, dass für visitorNumber ein locale-spezifisches Dezimaltrennzeichen verwendet werden soll. Somit ist ein mögliches englisches Ergebnis

Welcome to ACME Corp., you are visitor number 1,300!

Auf Deutsch haben wir Ergebnisse wie

Besucher Nr. 1.300, willkommen bei ACME Corp.!

8.3.10 Text-Templating über untagged Template-Literale

Nehmen wir an, wir möchten HTML erstellen, das die folgenden Daten in einer Tabelle anzeigt.

const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];

Wie bereits erklärt, sind Template-Literale keine Templates.

Ein Template ist im Grunde eine Funktion: Daten rein, Text raus. Und diese Beschreibung gibt uns einen Hinweis, wie wir ein Template-Literal in ein tatsächliches Template verwandeln können. Implementieren wir ein Template tmpl als Funktion, die ein Array addrs auf einen String abbildet.

const tmpl = addrs => `
    <table>
    ${addrs.map(addr => `
        <tr><td>${addr.first}</td></tr>
        <tr><td>${addr.last}</td></tr>
    `).join('')}
    </table>
`;
console.log(tmpl(data));
// Output:
// <table>
//
//     <tr><td><Jane></td></tr>
//     <tr><td>Bond</td></tr>
//
//     <tr><td>Lars</td></tr>
//     <tr><td><Croft></td></tr>
//
// </table>

Das äußere Template-Literal liefert die Klammerung <table> und </table>. Darin betten wir JavaScript-Code ein, der einen String durch Verketten eines Arrays von Strings erzeugt. Das Array wird erstellt, indem jeder Eintrag in zwei Tabellenzeilen abgebildet wird. Beachten Sie, dass die einfachen Textstücke <Jane> und <Croft> nicht ordnungsgemäß escaped sind. Wie das über ein Tagged Template geschieht, wird im nächsten Abschnitt erklärt.

8.3.10.1 Sollte ich diese Technik im Produktionscode verwenden?

Dies ist eine nützliche schnelle Lösung für kleinere Templating-Aufgaben. Für größere Aufgaben möchten Sie möglicherweise leistungsfähigere Lösungen wie die Templating-Engine Handlebars.js oder die in React verwendete JSX-Syntax verwenden.

Anerkennung: Dieser Ansatz zum Text-Templating basiert auf einer Idee von Claus Reinke.

8.3.11 Eine Tag-Funktion für HTML-Templating

Im Vergleich zur Verwendung von untagged Templates für HTML-Templating, wie wir es im vorherigen Abschnitt getan haben, bieten Tagged Templates zwei Vorteile:

Dann sieht der Code für das Template wie folgt aus. Der Name der Tag-Funktion ist html.

const tmpl = addrs => html`
    <table>
    ${addrs.map(addr => html`
        <tr><td>!${addr.first}</td></tr>
        <tr><td>!${addr.last}</td></tr>
    `)}
    </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// Output:
// <table>
//
//     <tr><td>&lt;Jane&gt;</td></tr>
//     <tr><td>Bond</td></tr>
//
//     <tr><td>Lars</td></tr>
//     <tr><td>&lt;Croft&gt;</td></tr>
//
// </table>

Beachten Sie, dass die spitzen Klammern um Jane und Croft escaped sind, während die um tr und td nicht.

Wenn Sie eine Substitution mit einem Ausrufezeichen (!${addr.first}) versehen, wird sie HTML-escaped. Die Tag-Funktion prüft den Text vor einer Substitution, um zu bestimmen, ob sie escaped oder nicht.

Eine Implementierung von html wird später gezeigt.

8.4 Implementierung von Tag-Funktionen

Das Folgende ist ein Tagged Template-Literal.

tagFunction`lit1\n${subst1} lit2 ${subst2}`

Dieses Literal löst (ungefähr) den folgenden Funktionsaufruf aus:

tagFunction(['lit1\n',  ' lit2 ', ''], subst1, subst2)

Der exakte Funktionsaufruf sieht eher so aus:

// Globally: add template object to per-realm template map
{
    // “Cooked” template strings: backslash is interpreted
    const templateObject = ['lit1\n',  ' lit2 ', ''];
    // “Raw” template strings: backslash is verbatim
    templateObject.raw   = ['lit1\\n', ' lit2 ', ''];

    // The Arrays with template strings are frozen
    Object.freeze(templateObject.raw);
    Object.freeze(templateObject);

    __templateMap__[716] = templateObject;
}

// In-place: invocation of tag function
tagFunction(__templateMap__[716], subst1, subst2)

Es gibt zwei Arten von Eingaben, die die Tag-Funktion erhält:

  1. Template-Strings (erster Parameter): die statischen Teile von Tagged Templates, die sich nicht ändern (z. B. ' lit2 '). Ein Template-Objekt speichert zwei Versionen der Template-Strings.
    • Gekocht: mit interpretierten Escapes wie \n. Gespeichert in templateObject[0] usw.
    • Roh: mit uninterpretierten Escapes. Gespeichert in templateObject.raw[0] usw.
  2. Substitutionen (verbleibende Parameter): die Werte, die über ${} in Template-Literale eingebettet werden (z. B. subst1). Substitutionen sind dynamisch, sie können sich bei jeder Ausführung ändern.

Die Idee hinter einem globalen Template-Objekt ist, dass dasselbe Tagged Template mehrmals ausgeführt werden kann (z. B. in einer Schleife oder einer Funktion). Das Template-Objekt ermöglicht es der Tag-Funktion, Daten aus früheren Aufrufen zu cachen: Sie kann Daten, die sie aus Eingabeart #1 (Template-Strings) abgeleitet hat, in das Objekt legen, um eine Neuberechnung zu vermeiden. Caching erfolgt pro *Realm* (stellen Sie sich einen Frame in einem Browser vor). Das heißt, es gibt ein Template-Objekt pro Aufrufstelle und Realm.

8.4.1 Anzahl von Template-Strings im Vergleich zur Anzahl von Substitutionen

Verwenden wir die folgende Tag-Funktion, um zu untersuchen, wie viele Template-Strings es im Vergleich zu Substitutionen gibt.

function tagFunc(templateObject, ...substs) {
    return { templateObject, substs };
}

Die Anzahl der Template-Strings ist immer eins mehr als die Anzahl der Substitutionen. Mit anderen Worten: Jede Substitution ist immer von zwei Template-Strings umgeben.

templateObject.length === substs.length + 1

Wenn eine Substitution zuerst in einem Literal steht, wird sie von einem leeren Template-String vorangestellt.

> tagFunc`${'subst'}xyz`
{ templateObject: [ '', 'xyz' ], substs: [ 'subst' ] }

Wenn eine Substitution zuletzt in einem Literal steht, wird sie von einem leeren Template-String angehängt.

> tagFunc`abc${'subst'}`
{ templateObject: [ 'abc', '' ], substs: [ 'subst' ] }

Ein leeres Template-Literal erzeugt einen Template-String und keine Substitutionen.

> tagFunc``
{ templateObject: [ '' ], substs: [] }

8.4.2 Escaping in Tagged Template-Literale: „cooked“ gegen „raw“

Template-Strings sind in zwei Interpretationen verfügbar – „cooked“ und „raw“. Diese Interpretationen beeinflussen das Escaping.

Die Tag-Funktion describe erlaubt uns zu untersuchen, was das bedeutet.

function describe(tmplObj, ...substs) {
    return {
        Cooked: merge(tmplObj, substs),
        Raw: merge(tmplObj.raw, substs),
    };
}
function merge(tmplStrs, substs) {
    // There is always at least one element in tmplStrs
    let result = tmplStrs[0];
    substs.forEach((subst, i) => {
        result += String(subst);
        result += tmplStrs[i+1];
    });
    return result;
}

Verwenden wir diese Tag-Funktion:

> describe`${3+3}`
{ Cooked: '6', Raw: '6' }

> describe`\${3+3}`
{ Cooked: '${3+3}', Raw: '\\${3+3}' }

> describe`\\${3+3}`
{ Cooked: '\\6', Raw: '\\\\6' }

> describe`\``
{ Cooked: '`', Raw: '\\`' }

Wie Sie sehen können, gibt es, wenn die gekochte Interpretation eine Substitution oder einen Backtick hat, auch in der rohen Interpretation eine. Allerdings erscheinen alle Backslashes aus dem Literal in der rohen Interpretation.

Andere Vorkommen des Backslash werden wie folgt interpretiert:

Zum Beispiel

> describe`\\`
{ Cooked: '\\', Raw: '\\\\' }

> describe`\n`
{ Cooked: '\n', Raw: '\\n' }

> describe`\u{58}`
{ Cooked: 'X', Raw: '\\u{58}' }

Zusammenfassend lässt sich sagen: Die einzige Auswirkung, die der Backslash im rohen Modus hat, ist, dass er Substitutionen und Backticks escapet.

8.4.3 Beispiel: String.raw

So würden Sie String.raw implementieren.

function raw(strs, ...substs) {
    let result = strs.raw[0];
    for (const [i,subst] of substs.entries()) {
        result += subst;
        result += strs.raw[i+1];
    }
    return result;
}

8.4.4 Beispiel: Implementierung einer Tag-Funktion für HTML-Templating

Ich habe zuvor die Tag-Funktion html für HTML-Templating demonstriert.

const tmpl = addrs => html`
    <table>
    ${addrs.map(addr => html`
        <tr><td>!${addr.first}</td></tr>
        <tr><td>!${addr.last}</td></tr>
    `)}
    </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// Output:
// <table>
//
//     <tr><td>&lt;Jane&gt;</td></tr>
//     <tr><td>Bond</td></tr>
//
//     <tr><td>Lars</td></tr>
//     <tr><td>&lt;Croft&gt;</td></tr>
//
// </table>

Wenn Sie einer Substitution ein Ausrufezeichen voranstellen (!${addr.first}), wird sie HTML-escaped. Die Tag-Funktion prüft den Text vor einer Substitution, um zu bestimmen, ob sie escaped oder nicht.

Dies ist eine Implementierung von html.

function html(templateObject, ...substs) {
    // Use raw template strings: we don’t want
    // backslashes (\n etc.) to be interpreted
    const raw = templateObject.raw;

    let result = '';

    substs.forEach((subst, i) => {
        // Retrieve the template string preceding
        // the current substitution
        let lit = raw[i];

        // In the example, map() returns an Array:
        // If `subst` is an Array (and not a string),
        // we turn it into a string
        if (Array.isArray(subst)) {
            subst = subst.join('');
        }

        // If the substitution is preceded by an exclamation
        // mark, we escape special characters in it
        if (lit.endsWith('!')) {
            subst = htmlEscape(subst);
            lit = lit.slice(0, -1);
        }
        result += lit;
        result += subst;
    });
    // Take care of last template string
    result += raw[raw.length-1]; // (A)

    return result;
}

Es gibt immer eine Substitution mehr als Template-Strings, weshalb wir den letzten Template-String in Zeile A anhängen müssen.

Die folgende ist eine einfache Implementierung von htmlEscape().

function htmlEscape(str) {
    return str.replace(/&/g, '&amp;') // first!
              .replace(/>/g, '&gt;')
              .replace(/</g, '&lt;')
              .replace(/"/g, '&quot;')
              .replace(/'/g, '&#39;')
              .replace(/`/g, '&#96;');
}
8.4.4.1 Weitere Ideen

Es gibt noch mehr Dinge, die Sie mit diesem Ansatz zum Templating tun können.

8.4.5 Beispiel: Zusammenstellen von regulären Ausdrücken

Es gibt zwei Möglichkeiten, Instanzen von regulären Ausdrücken zu erstellen.

Wenn Sie letzteres verwenden, dann weil Sie bis zur Laufzeit warten müssen, damit alle notwendigen Zutaten verfügbar sind. Sie erstellen den regulären Ausdruck durch Verketten von drei Arten von Teilen:

  1. Statischer Text
  2. Dynamische reguläre Ausdrücke
  3. Dynamischer Text

Für #3 müssen Sonderzeichen (Punkte, eckige Klammern usw.) escaped werden, während #1 und #2 unverändert verwendet werden können. Eine Tag-Funktion für reguläre Ausdrücke regex kann bei dieser Aufgabe helfen.

const INTEGER = /\d+/;
const decimalPoint = '.'; // locale-specific! E.g. ',' in Germany
const NUMBER = regex`${INTEGER}(${decimalPoint}${INTEGER})?`;

regex sieht so aus:

function regex(tmplObj, ...substs) {
    // Static text: verbatim
    let regexText = tmplObj.raw[0];
    for ([i, subst] of substs.entries()) {
        if (subst instanceof RegExp) {
            // Dynamic regular expressions: verbatim
            regexText += String(subst);
        } else {
            // Other dynamic data: escaped
            regexText += quoteText(String(subst));
        }
        // Static text: verbatim
        regexText += tmplObj.raw[i+1];
    }
    return new RegExp(regexText);
}
function quoteText(text) {
    return text.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&');
}

8.5 FAQ: Template-Literale und Tagged Template-Literale

8.5.1 Woher stammen Template-Literale und Tagged Template-Literale?

Template-Literale und Tagged Template-Literale wurden aus der Sprache E übernommen, die diese Funktion quasi literals nennt.

8.5.2 Was ist der Unterschied zwischen Makros und Tagged Template-Literale?

Makros ermöglichen es Ihnen, Sprachkonstrukte mit benutzerdefinierter Syntax zu implementieren. Es ist schwierig, Makros für eine Programmiersprache bereitzustellen, deren Syntax so komplex ist wie die von JavaScript. Die Forschung in diesem Bereich läuft (siehe Mozillas sweet.js).

Während Makros für die Implementierung von Subsprachen viel leistungsfähiger sind als Tagged Templates, hängen sie von der Tokenisierung der Sprache ab. Daher sind Tagged Templates komplementär, da sie sich auf Textinhalte spezialisieren.

8.5.3 Kann ich ein Template-Literal aus einer externen Quelle laden?

Was, wenn ich ein Template-Literal wie `Hallo ${name}!` aus einer externen Quelle (z. B. einer Datei) laden möchte?

Sie missbrauchen Template-Literale, wenn Sie dies tun. Da ein Template-Literal beliebige Ausdrücke enthalten kann und ein Literal ist, ist das Laden von irgendwo anders ähnlich wie das Laden eines Ausdrucks oder eines String-Literals – Sie müssen eval() oder etwas Ähnliches verwenden.

8.5.4 Warum sind Backticks die Trennzeichen für Template-Literale?

Der Backtick war eines der wenigen ASCII-Zeichen, die in JavaScript noch nicht verwendet wurden. Die Syntax ${} für Interpolation ist sehr verbreitet (Unix-Shells usw.).

8.5.5 Hießen Template-Literale früher nicht Template-Strings?

Die Terminologie für Template-Literale änderte sich relativ spät während der Erstellung der ES6-Spezifikation. Die folgenden sind die alten Begriffe:

Weiter: 9. Variablen und Scoping