ES6語法總結(4)--字符串的擴展(2)

字符串的擴展

  1. 模板字符串javascript

  2. 實例:模板編譯html

  3. 標籤模板java

  4. String.raw()git

  5. 模板字符串的限制github

1.模板字符串

傳統的 JavaScript 語言,輸出模板一般是這樣寫的(下面使用了 jQuery 的方法)。web

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面這種寫法至關繁瑣不方便,ES6 引入了模板字符串解決這個問題。正則表達式

$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);

模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。數組

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is not legal.`

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

// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

上面代碼中的模板字符串,都是用反引號表示。若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。app

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

若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出之中。svg

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);

上面代碼中,全部模板字符串的空格和換行,都是被保留的,好比<ul>標籤前面會有一個換行。若是你不想要這個換行,可使用trim方法消除它。

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());

模板字符串中嵌入變量,須要將變量名寫在${}之中。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 傳統寫法爲
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。

let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

模板字符串之中還能調用函數。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。好比,大括號中是一個對象,將默認調用對象的toString方法。

若是模板字符串中的變量沒有聲明,將報錯。

// 變量place沒有聲明
let msg = `Hello, ${place}`;
// 報錯

因爲模板字符串的大括號內部,就是執行 JavaScript 代碼,所以若是大括號內部是一個字符串,將會原樣輸出。

`Hello ${'World'}`
// "Hello World"

模板字符串甚至還能嵌套。

const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')}
  </table>
`;

上面代碼中,模板字符串的變量之中,又嵌入了另外一個模板字符串,使用方法以下。

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

console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>

若是須要引用模板字符串自己,在須要時執行,能夠像下面這樣寫。

// 寫法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 寫法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"

2.實例:模板編譯

下面,咱們來看一個經過模板字符串,生成正式模板的實例。

let template = ` <ul> <% for(let i=0; i < data.supplies.length; i++) { %> <li><%= data.supplies[i] %></li> <% } %> </ul> `;

上面代碼在模板字符串之中,放置了一個常規模板。該模板使用<%...%>放置 JavaScript 代碼,使用<%= ... %>輸出 JavaScript 表達式。

怎麼編譯這個模板字符串呢?

一種思路是將其轉換爲 JavaScript 表達式字符串。

echo('<ul>');
for(let i=0; i < data.supplies.length; i++) {
  echo('<li>');
  echo(data.supplies[i]);
  echo('</li>');
};
echo('</ul>');

這個轉換使用正則表達式就好了。

let evalExpr = /<%=(.+?)%>/g;
let expr = /<%([\s\S]+?)%>/g;

template = template
  .replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
  .replace(expr, '`); \n $1 \n echo(`');

template = 'echo(`' + template + '`);';

而後,將template封裝在一個函數裏面返回,就能夠了。

let script =
`(function parse(data){ let output = ""; function echo(html){ output += html; } ${ template } return output; })`;

return script;

將上面的內容拼裝成一個模板編譯函數compile

function compile(template){
  const evalExpr = /<%=(.+?)%>/g;
  const expr = /<%([\s\S]+?)%>/g;

  template = template
    .replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
    .replace(expr, '`); \n $1 \n echo(`');

  template = 'echo(`' + template + '`);';

  let script =
  `(function parse(data){ let output = ""; function echo(html){ output += html; } ${ template } return output; })`;

  return script;
}

compile函數的用法以下。

let parse = eval(compile(template));
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });
// <ul>
// <li>broom</li>
// <li>mop</li>
// <li>cleaner</li>
// </ul>

3.標籤模板

模板字符串的功能,不只僅是上面這些。它能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串。這被稱爲「標籤模板」功能(tagged template)。

alert`123`
// 等同於
alert(123)

標籤模板其實不是模板,而是函數調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。

可是,若是模板字符裏面有變量,就不是簡單的調用了,而是會將模板字符串先處理成多個參數,再調用函數。

let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同於
tag(['Hello ', ' world ', ''], 15, 50);

上面代碼中,模板字符串前面有一個標識名tag,它是一個函數。整個表達式的返回值,就是tag函數處理模板字符串後的返回值。

函數tag依次會接收到多個參數。

function tag(stringArr, value1, value2){
  // ...
}

// 等同於

function tag(stringArr, ...values){
  // ...
}

tag函數的第一個參數是一個數組,該數組的成員是模板字符串中那些沒有變量替換的部分,也就是說,變量替換隻發生在數組的第一個成員與第二個成員之間、第二個成員與第三個成員之間,以此類推。

tag函數的其餘參數,都是模板字符串各個變量被替換後的值。因爲本例中,模板字符串含有兩個變量,所以tag會接受到value1value2兩個參數。

tag函數全部參數的實際值以下。

  • 第一個參數:['Hello ', ' world ', '']
  • 第二個參數: 15
  • 第三個參數:50

也就是說,tag函數實際上如下面的形式調用。

tag(['Hello ', ' world ', ''], 15, 50)

咱們能夠按照須要編寫tag函數的代碼。下面是tag函數的一種寫法,以及運行結果。

let a = 5;
let b = 10;

function tag(s, v1, v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  console.log(v1);
  console.log(v2);

  return "OK";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"

下面是一個更復雜的例子。

let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;

function passthru(literals) {
  let result = '';
  let i = 0;

  while (i < literals.length) {
    result += literals[i++];
    if (i < arguments.length) {
      result += arguments[i];
    }
  }

  return result;
}

msg // "The total is 30 (31.5 with tax)"

上面這個例子展現了,如何將各個參數按照原來的位置拼合回去。

passthru函數採用 rest 參數的寫法以下。

function passthru(literals, ...values) {
  let output = "";
  let index;
  for (index = 0; index < values.length; index++) {
    output += literals[index] + values[index];
  }

  output += literals[index]
  return output;
}

「標籤模板」的一個重要應用,就是過濾 HTML 字符串,防止用戶輸入惡意內容。

let message =
  SaferHTML`<p>${sender} has sent you a message.</p>`;

function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let arg = String(arguments[i]);

    // Escape special characters in the substitution.
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

上面代碼中,sender變量每每是用戶提供的,通過SaferHTML函數處理,裏面的特殊字符都會被轉義。

let sender = '<script>alert("abc")</script>'; // 惡意代碼
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;

message
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>

標籤模板的另外一個應用,就是多語言轉換(國際化處理)。

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "歡迎訪問xxx,您是第xxxx位訪問者!"

模板字符串自己並不能取代 Mustache 之類的模板庫,由於沒有條件判斷和循環處理功能,可是經過標籤函數,你能夠本身添加這些功能。

// 下面的hashTemplate函數
// 是一個自定義的模板處理函數
let libraryHtml = hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul> `;

除此以外,你甚至可使用標籤模板,在 JavaScript 語言之中嵌入其餘語言。

jsx` <div> <input ref='input' onChange='${this.handleChange}' defaultValue='${this.state.value}' /> ${this.state.value} </div> `

上面的代碼經過jsx函數,將一個 DOM 字符串轉爲 React 對象。你能夠在 GitHub 找到jsx函數的具體實現

下面則是一個假想的例子,經過java函數,在 JavaScript 代碼之中運行 Java 代碼。

java` class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); // Display the string. } } `
HelloWorldApp.main();

模板處理函數的第一個參數(模板字符串數組),還有一個raw屬性。

console.log`123`
// ["123", raw: Array[1]]

上面代碼中,console.log接受的參數,其實是一個數組。該數組有一個raw屬性,保存的是轉義後的原字符串。

請看下面的例子。

tag`First line\nSecond line`

function tag(strings) {
  console.log(strings.raw[0]);
  // strings.raw[0] 爲 "First line\\nSecond line"
  // 打印輸出 "First line\nSecond line"
}

上面代碼中,tag函數的第一個參數strings,有一個raw屬性,也指向一個數組。該數組的成員與strings數組徹底一致。好比,strings數組是["First line\nSecond line"],那麼strings.raw數組就是["First line\\nSecond line"]。二者惟一的區別,就是字符串裏面的斜槓都被轉義了。好比,strings.raw 數組會將\n視爲\\n兩個字符,而不是換行符。這是爲了方便取得轉義以前的原始模板而設計的。

4.String.raw()

ES6 還爲原生的 String 對象,提供了一個raw方法。

String.raw方法,每每用來充當模板字符串的處理函數,返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字符串,對應於替換變量後的模板字符串。

String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"

String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!"

若是原字符串的斜槓已經轉義,那麼String.raw會進行再次轉義。

String.raw`Hi\\n`
// 返回 "Hi\\\\n"

String.raw方法能夠做爲處理模板字符串的基本方法,它會將全部變量替換,並且對斜槓進行轉義,方便下一步做爲字符串來使用。

String.raw方法也能夠做爲正常的函數使用。這時,它的第一個參數,應該是一個具備raw屬性的對象,且raw屬性的值應該是一個數組。

String.raw({ raw: 'test' }, 0, 1, 2);
// 't0e1s2t'

// 等同於
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);

做爲函數,String.raw的代碼實現基本以下。

String.raw = function (strings, ...values) {
  let output = '';
  let index;
  for (index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }

  output += strings.raw[index]
  return output;
}

5.模板字符串的限制

前面提到標籤模板裏面,能夠內嵌其餘語言。可是,模板字符串默認會將字符串轉義,致使沒法嵌入其餘語言。

舉例來講,標籤模板裏面能夠嵌入 LaTEX 語言。

function latex(strings) {
  // ...
}

let document = latex` \newcommand{\fun}{\textbf{Fun!}} // 正常工做 \newcommand{\unicode}{\textbf{Unicode!}} // 報錯 \newcommand{\xerxes}{\textbf{King!}} // 報錯 Breve over the h goes \u{h}ere // 報錯 `

上面代碼中,變量document內嵌的模板字符串,對於 LaTEX 語言來講徹底是合法的,可是 JavaScript 引擎會報錯。緣由就在於字符串的轉義。

模板字符串會將\u00FF\u{42}看成 Unicode 字符進行轉義,因此\unicode解析時報錯;而\x56會被看成十六進制字符串轉義,因此\xerxes會報錯。也就是說,\u\x在 LaTEX 裏面有特殊含義,可是 JavaScript 將它們轉義了。

爲了解決這個問題,ES2018 放鬆了對標籤模板裏面的字符串轉義的限制。若是遇到不合法的字符串轉義,就返回undefined,而不是報錯,而且從raw屬性上面能夠獲得原始字符串。

function tag(strs) {
  strs[0] === undefined
  strs.raw[0] === "\\unicode and \\u{55}";
}
tag`\unicode and \u{55}`

上面代碼中,模板字符串本來是應該報錯的,可是因爲放鬆了對字符串轉義的限制,因此不報錯了,JavaScript 引擎將第一個字符設置爲undefined,可是raw屬性依然能夠獲得原始字符串,所以tag函數仍是能夠對原字符串進行處理。

注意,這種對字符串轉義的放鬆,只在標籤模板解析字符串時生效,不是標籤模板的場合,依然會報錯。

let bad = `bad escape sequence: \unicode`; // 報錯
相關文章
相關標籤/搜索