การอ่านค่าลิเทอรัลด้วยสตริงเทมเพลต ES6

Addy Osmani
Addy Osmani

สตริงใน JavaScript มีข้อจำกัดมาอย่างยาวนาน ขาดความสามารถที่ผู้ใช้คาดหวังจากภาษาอย่าง Python หรือ Ruby สตริงเทมเพลต ES6 (ใช้ได้ใน Chrome 41 ขึ้นไป) เปลี่ยนแปลงสิ่งนั้นอย่างพื้นฐาน ซึ่งจะแนะนำวิธีกำหนดสตริงด้วยภาษาเฉพาะโดเมน (DSL) ซึ่งจะทําให้สิ่งต่อไปนี้ดีขึ้น

  • การแทรกสตริง
  • นิพจน์ที่ฝัง
  • สตริงหลายบรรทัดโดยไม่ต้องใช้แฮ็ก
  • การจัดรูปแบบสตริง
  • การติดแท็กสตริงสำหรับการหลีกหนี HTML ที่ปลอดภัย การจัดทําให้เป็นภาษาท้องถิ่น และอื่นๆ

สตริงเทมเพลตใช้วิธีแก้ปัญหาเหล่านี้แตกต่างออกไปโดยสิ้นเชิง แทนที่จะยัดฟีเจอร์อีกอย่างลงในสตริงอย่างที่เรารู้จักในปัจจุบัน

ไวยากรณ์

สตริงเทมเพลตใช้เครื่องหมายแบ็กทิก (``) แทนเครื่องหมายคำพูดเดี่ยวหรือคู่ที่เราคุ้นเคยกับสตริงทั่วไป ดังนั้นสตริงเทมเพลตจึงเขียนได้ดังนี้

var greeting = `Yo World!`;

ที่ผ่านมาสตริงเทมเพลตไม่ได้ให้อะไรมากกว่าสตริงปกติ เรามาเปลี่ยนเรื่องนี้กัน

การแทนที่สตริง

ประโยชน์ที่แท้จริงอย่างหนึ่งคือการเปลี่ยนสตริง การเปลี่ยนค่าช่วยให้เราใช้นิพจน์ JavaScript ที่ถูกต้อง (รวมถึงการเพิ่มตัวแปร) และภายใน Template Literal ได้ โดยผลลัพธ์จะแสดงผลเป็นส่วนหนึ่งของสตริงเดียวกัน

สตริงเทมเพลตอาจมีตัวยึดตําแหน่งสําหรับการแทนที่สตริงโดยใช้ไวยากรณ์ ${ } ดังที่แสดงด้านล่าง

// 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

หากต้องการใช้เครื่องหมายแบ็กทิกภายในสตริง ให้กำหนดเป็นอักขระหลีกโดยใช้อักขระแบ็กสแลช \ ดังนี้

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 ของเราจะใช้อาร์กิวเมนต์ 2 รายการ ได้แก่ ชื่อผู้ใช้และความคิดเห็น โดยทั้ง 2 รายการอาจมีอักขระที่ไม่ปลอดภัยของ 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 ในทางปฏิบัติ หากคุณต้องการใช้ฟีเจอร์เหล่านี้ในเวอร์ชันที่ใช้งานจริงในปัจจุบัน ฟีเจอร์เหล่านี้จะได้รับการรองรับใน Transpiler หลักของ ES6 ซึ่งรวมถึง Traceur และ 6to5 ดูตัวอย่างสตริงเทมเพลตในรีโปตัวอย่างของ Chrome หากต้องการลองใช้ คุณอาจสนใจรายการเทียบเท่าของ ES6 ใน ES5 ซึ่งแสดงวิธีใช้ Template String รูปแบบต่างๆ โดยใช้ ES5 ในปัจจุบัน

สตริงเทมเพลตจะเพิ่มความสามารถที่สำคัญหลายอย่างให้กับ JavaScript ซึ่งรวมถึงวิธีที่ดียิ่งขึ้นในการแทรกสตริงและนิพจน์ สตริงหลายบรรทัด และความสามารถในการสร้าง DSL ของคุณเอง

ฟีเจอร์ที่สําคัญที่สุดอย่างหนึ่งที่มาพร้อมกับเทมเพลตที่ติดแท็กคือฟีเจอร์สําคัญสําหรับการเขียน DSL ดังกล่าว โดยจะได้รับส่วนของสตริงเทมเพลตเป็นอาร์กิวเมนต์ จากนั้นคุณก็เลือกวิธีใช้สตริงและการแทนที่เพื่อกำหนดเอาต์พุตสุดท้ายของสตริงได้

อ่านเพิ่มเติม