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

אדי אוסמאני
אדי אוסמאני

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

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

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

תחביר

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

var greeting = `Yo World!`;

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

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

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

מחרוזות של תבניות יכולות להכיל 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!`

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

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

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

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

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

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

<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. בפועל, אם רוצים להשתמש בהם בייצור כבר היום, הם נתמכים במערכות Transpilers מובילות של ES6, כולל Traceur ו-6to5. אם אתם רוצים לנסות את המחרוזות האלה, מומלץ לעיין בדוגמה למחרוזות תבנית מתוך מאגר הדוגמאות של Chrome. אולי יעניין אותך גם ES6 שווי ערך ב-ES5, שמדגים איך להשיג חלק מהמחרוזות של תבנית הסוכר שעושות שימוש ב-ES5 היום.

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

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

קריאה נוספת