模板字面量(Template Literal)是一種能夠嵌入表達式的格式化字元串,有別於普通字元串,它使用反引號(`)包裹字元序列,而不是雙引號或單引號。模板字面量包含特定形式的佔位符(${expression}),由美元符號、大括弧以及合法的表達式組成,合法的表達式(expression)可以是變數、算術或函數調用,甚至還可以是模板字面量。在ES6引入模板字面量後,就能避免用若干個加號來實現字元串拼接,而改用更為優雅的語法來替代,下面用新舊兩種方式分別來組合字元串。

var name = "strick",
age = 29, str;
str = "My name is "" + name + "". M y age is " + age + "."; //傳統拼接方式
str = `My name is "${name}". My age is ${age}.`;      //模板字面量方式

  對比上面兩條賦值語句可以得出,無論是代碼可讀性還是簡潔性,新方式都要略勝一籌。因為舊方式中的普通字元串是用雙引號包裹的,所以字元序列中的雙引號要用反斜線()轉義。而模板字面量則無需為雙引號或單引號轉義,但如果出現反引號,那麼就得將其轉義。

一、佔位符

1)表達式

  在佔位符內,表達式的計算結果會按照一定的規則轉換成字元串,如果計算結果是數字、布爾值、對象或數組等,那麼就調它們內置的toString()方法;而如果是null或undefined,那麼就用String()函數實現類型轉換,具體如下所示。

`${"abc"}`; //"abc"
`${123}`; //"123"
`${true}`; //"true"
`${null}`; //"null"
`${undefined}`; //"undefined"
`${{ id: 1 }}`; //"[object Object]"
`${[1, 2, 3]}`; //"1,2,3"

  有一點要引起注意,那就是佔位符中的變數必須先聲明(可以不初始化),否則將拋出未定義的引用錯誤。下面的school變數就沒有預先聲明,而直接在模板字面量中使用。

`I am studying at ${school}.`; //拋出未定義的引用錯誤

  前面曾提到過佔位符中的表達式可以是模板字面量,這讓佔位符變得非常靈活,可以適應更多的場景,處理更為複雜的問題。例如有這麼一個需求,為Chrome瀏覽器中的CSS屬性添加瀏覽器前綴-webkit-,可以像下面這樣編寫。

let attr = "border-radius";
function isChrome() {
return true; //為了簡化演示,省略了瀏覽器嗅探邏輯
}
attr = `${isChrome() ? `-webkit-${attr}` : attr}`;
console.log(attr); //"-webkit-border-radius"

2)作用域

  在佔位符中的變數,它的作用域和定義模板字面量時所處的位置有關,而不是調用時的位置。以下面代碼為例,有3個同名的scope變數,分別定義在全局作用域、outer1()函數和inner()函數中,模板字面量作為一個實參傳遞給inner()函數,最後在inner()函數中把模板字面量輸出到控制台。

var scope = "global"; //全局變數
function outer1() {
var scope = "outer";
function inner(str) {
var scope = "inner";
console.log(str);
}
inner(`current ${scope}`);
}
outer1();   //"current outer"

  根據前面的作用域規則可知,得到的結果是「current outer」。如果模板字面量所處的作用域中沒有該變數,那麼就會沿著作用域鏈向上搜索,直到全局作用域為止。在下面的代碼中,注釋了outer2()函數中的scope變數,得到的結果為「current global」。

var scope = "global"; //全局變數
function outer2() {
//var scope = "outer";
function inner(str) {
var scope = "inner";
console.log(str);
}
inner(`current ${scope}`);
}
outer2();   //"current global"

二、多行字元串

  在ES6之前,如果要創建多行字元串,那麼得像下面這樣間接實現。

let multi1 = "first line
"
+
"second line
"
+
"third line";
let multi2 = "first line

second line

third line";

  第一種是將多段字元串用加號拼接,第二種是在換行之前使用反斜線()。雖然是兩種實現方法,但兩者都需要添加換行符(
)。而在ES6引入了模板字面量後,就能原生支持多行字元串,不需要再用上述權宜之計了。具體如下所示,既不需要加號和反斜線,也不需要換行符,代碼言簡意賅。

let multi3 = `first line
second line
third line`;

  注意,模板字面量能識別空白符,像上面代碼中的多行字元串,其第二行和第三行的開頭都包含了空白符,因此在輸出時都會有縮進。

三、標籤模板

  模板字面量雖然強大,但也有它的局限性,例如下面兩點:

(1)有可能會遭受XSS(跨站腳本攻擊)攻擊,因為無法轉義HTML中的特殊字元(例如「<」、「>」等)。

(2)不能替代模板引擎(例如Mustache、Handlebars等),因為無法在佔位符中使用if、while等語句。

1)調用

  為了解決上述問題,ES6引入了標籤模板(Tagged Template)。標籤模板並不是模板,而是一種特殊方式的函數調用,如下所示。

func`<p>${name}</p><p>${age}</p>`;

  調用func()函數的時候省略了圓括弧,函數名後面直接跟模板字面量,這就是標籤模板的調用方式。它一般會包含兩個參數,第一個是由沒有被替換的部分組成的數組,第二個是剩餘參數,包含了所有佔位符中的計算結果。下面是一個完整的標籤模板的示例。

function func(literals, ...substitutions) {
console.log(literals); //["<p>", "</p><p>", "</p>", raw]
console.log(substitutions); //["strick", 29]
}
var name = "strick",
age = 29;
func`<p>${name}</p><p>${age}</p>`;

  注意觀察兩個參數的輸出結果。literals數組包含三個元素和一個特殊的raw屬性。三個元素分別是第一個佔位符之前的部分,兩個佔位符之間的部分和第二個佔位符後面的部分。剩餘參數substitutions由兩個佔位符中所引用的變數name和age的值組成。

2)raw屬性

  這裡重點提一下raw屬性,它也是一個數組,包含了literals數組中的三個元素所對應的原始信息,相當於為每個元素調用了一次String對象的raw()方法。注意,String.raw()是一個內置的標籤模板,在調用時要用特殊的形式。

  下面用一個例子來演示String.raw()的功能,先定義一個包含水平製表符( )的字元串,然後在第一次輸出的時候,「<p>」和「</p>」之間會有空格隔開,接著調用String.raw(),再次輸出時就能把「 」也一併顯示。其實要在控制台顯示第二條注釋需要在「 」前加一條反斜線(即「<p>\t</p>」)做轉義,這樣才能把「 」分成兩個獨立的字元:「」和「t」,不再有水平製表符的效果。但此處為了便於理解,省略了反斜線。

let html = `<p>t</p>`;
console.log(html); //"<p> </p>"
html = String.raw`<p>t</p>`;
console.log(html); //"<p> </p>"

推薦閱讀:

相关文章