קבלת טקסט מילולי עם מחרוזות של תבנית ES6

Addy Osmani
Addy Osmani

מבחינה היסטורית, מחרוזות ב-JavaScript היו מוגבלות, והן לא כללו את היכולות שאפשר לצפות להן בשפות כמו Python או Ruby. מחרוזות תבנית ב-ES6 (זמינות ב-Chrome מגרסה 41 ואילך) משנות את המצב באופן מהותי. הם מאפשרים להגדיר מחרוזות באמצעות שפות ייעודיות לדומיין (DSL), וכך משפרים את:

  • אינטרפולציה של מחרוזות
  • ביטויים מוטמעים
  • מחרוזות בכמה שורות ללא טריקים
  • עיצוב מחרוזות
  • תיוג מחרוזות לצורך בריחה בטוחה מ-HTML, לוקליזציה ועוד.

במקום להוסיף עוד תכונה למחרוזות כפי שאנחנו מכירים אותן היום, מחרוזות תבנית מציעות דרך שונה לגמרי לפתרון הבעיות האלה.

תחביר

במחרוזות של תבניות נעשה שימוש בסוגריים ימניים (``) במקום במירכאות יחידות או כפולות שאנחנו רגילים להשתמש בהן במחרוזות רגילות. כך אפשר לכתוב מחרוזת תבנית:

var greeting = `Yo World!`;

עד כה, מחרוזות תבנית לא נתנו לנו יותר מאשר מחרוזות רגילות. נשנה את זה.

החלפת מחרוזות

אחד מהיתרונות הראשונים שלהם הוא החלפת מחרוזות. החלפה מאפשרת לנו להשתמש בכל ביטוי חוקי של JavaScript (כולל, למשל, הוספת משתנים), ובתוך Template Literal, התוצאה תוצג כחלק מאותה מחרוזת.

מחרוזות תבנית יכולות להכיל placeholders להחלפת מחרוזות באמצעות התחביר ${ }, כפי שמוצג בהמשך:

// Simple string substitution
var name = "Brendan";
console.log(`Yo, ${name}!`);

// => "Yo, Brendan!"

מאחר שכל החלפות המחרוזות במחרוזות התבנית הן ביטויים של JavaScript, אפשר להחליף הרבה יותר מאשר שמות של משתנים. לדוגמה, בהמשך אפשר להשתמש בביטוי אינטרפולציה כדי להטמיע נוסחאות מתמטיות בתוך הטקסט שאפשר לקרוא:

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.

הן גם שימושיות מאוד לפונקציות בתוך ביטויים:

function fn() { return "I am a result. Rarr"; }
console.log(`foo ${fn()} bar`);
//=> foo I am a result. Rarr bar.

ה-${} פועל מצוין עם כל סוג של ביטוי, כולל ביטויים של חברים וקריאות לשיטות:

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

אם אתם צריכים להשתמש בסוגריים בריבוע בתוך המחרוזת, תוכלו לסמן אותם בתווי בריחה (escape) באמצעות לוכסן הפוך \ באופן הבא:

var greeting = `\`Yo\` World!`;

מחרוזות בכמה שורות

במשך זמן מה, כדי להשתמש במחרוזות מרובות שורות ב-JavaScript נדרשו פתרונות זמניים לא יעילים. הפתרונות הקיימים לבעיות האלה דורשים שהמחרוזות יהיו בשורה אחת או שיחול עליהם פיצול למחרוזות בכמה שורות באמצעות \ (קו נטוי לאחור) לפני כל שורה חדשה. לדוגמה:

var greeting = "Yo \
World";

הקוד הזה אמור לפעול בצורה תקינה ברוב מנועי JavaScript המודרניים, אבל ההתנהגות עצמה עדיין נחשבת לפריצה. אפשר גם להשתמש בשרשור שרשור כדי לזייף תמיכה בכמה שורות, אבל גם כאן יש בעיות:

var greeting = "Yo " +
"World";

מחרוזות תבנית מפשטות באופן משמעותי מחרוזות בכמה שורות. פשוט מוסיפים שורות חדשות במקומות הנדרשים, והנה. לדוגמה:

כל רווח לבן בתוך התחביר של קו הנטוי לאחור ייחשב גם הוא כחלק מהמחרוזת.

console.log(`string text line 1
string text line 2`);

תבניות מתויגות

עד עכשיו התמקדנו בשימוש במחרוזות תבנית להחלפת מחרוזות וליצירת מחרוזות בכמה שורות. תכונה חזקה נוספת שהם מביאים היא תבניות מתויגות. תבניות מתויגות משנות מחרוזת תבנית על ידי הצבת שם פונקציה לפני מחרוזת התבנית. לדוגמה:

fn`Hello ${you}! You're looking ${adjective} today!`

הסמנטיקה של מחרוזת תבנית מתויגת שונה מאוד מזו של מחרוזת רגילה. למעשה, הן סוג מיוחד של קריאה לפונקציה: הקוד שלמעלה "מתפרק" ל-

fn(["Hello ", "! You're looking ", " today!"], you, adjective);

שימו לב שהארגומנט ה-(n + 1) תואם להחלפה שמתרחשת בין הערך ה-n לבין הערך ה-(n + 1) במערך המחרוזות. אפשר להשתמש באפשרות הזו למגוון דברים, אבל אחת מהשימושיות ביותר היא בריחה אוטומטית מכל משתנה שעבר אינטרפולציה.

לדוגמה, אפשר לכתוב פונקציה להמרת תווים ל-HTML כך ש…

html`<p title="${title}">Hello ${you}!</p>`

הפונקציה מחזירה מחרוזת עם המשתנים המתאימים שהוחלפו, אבל עם החלפה של כל התווים שאינם בטוחים ל-HTML. אשמח לעשות זאת. הפונקציה שלנו להמרת תווים ל-HTML תקבל שני ארגומנטים: שם משתמש ותגובה. שני השדות עשויים להכיל תווים לא בטוחים ב-HTML (כלומר ', ", <, > ו-&). לדוגמה, אם שם המשתמש הוא 'Domenic Denicola' והתגובה היא '& is a fun tag', הפלט צריך להיות:

<b>Domenic Denicola says:</b> "&amp; is a fun tag"

כך אפשר לכתוב את הפתרון של התבנית המתויגת:

// 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 = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
    },
    oUnescape = {
        '&amp;': '&',
        '&#38;': '&',
        '&lt;': '<',
        '&#60;': '<',
        '&gt;': '>',
        '&#62;': '>',
        '&apos;': "'",
        '&#39;': "'",
        '&quot;': '"',
        '&#34;': '"'
    },
    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>: "&amp; is a fun tag"

שימושים אפשריים אחרים כוללים בריחה אוטומטית, עיצוב, לוקליזציה ובאופן כללי, החלפות מורכבות יותר:

// 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}`;

סיכום

מחרוזות התבניות זמינות ב-Chrome 41 בטא ואילך, ב-IE Tech Preview, ב-Firefox 35 ואילך וב-io.js. אם אתם רוצים להשתמש בהם בסביבת הייצור כבר היום, הם נתמכים בממירי ES6 עיקריים, כולל Traceur ו-6to5. רוצים לנסות? אתם יכולים לבדוק את הדוגמה למחרוזות תבנית במאגר הדוגמאות של Chrome. כדאי גם לעיין במאמר מקבילות ל-ES6 ב-ES5, שבו מוסבר איך להשיג חלק מהיתרונות של Template Strings באמצעות ES5.

מחרוזות תבנית מאפשרות להשתמש ביכולות רבות וחשובות ב-JavaScript. השיפורים האלה כוללים דרכים טובות יותר לביצוע אינטרפולציה של מחרוזות וביטויים, מחרוזות בכמה שורות ויכולת ליצור שפות DSL משלכם.

אחת מהתכונות המשמעותיות ביותר שהן מספקות היא תבניות מתויגות – תכונה חיונית ליצירת שפות DSL כאלה. הם מקבלים את החלקים של מחרוזת התבנית כארגומנטים, ואז אתם יכולים להחליט איך להשתמש במחרוזות ובהחלפות כדי לקבוע את הפלט הסופי של המחרוזת.

מקורות מידע נוספים