ES6 系列之模板字符串

基礎用法

let message = `Hello World`;
console.log(message);
複製代碼

若是你碰巧要在字符串中使用反撇號,你可使用反斜槓轉義:html

let message = `Hello \` World`;
console.log(message);
複製代碼

值得一提的是,在模板字符串中,空格、縮進、換行都會被保留:git

let message = ` <ul> <li>1</li> <li>2</li> </ul> `;
console.log(message);
複製代碼

string

注意,打印的結果中第一行是一個換行,你可使用 trim 函數消除換行:github

let message = ` <ul> <li>1</li> <li>2</li> </ul> `.trim();
console.log(message);
複製代碼

string

嵌入變量

模板字符串支持嵌入變量,只須要將變量名寫在 ${} 之中,其實不止變量,任意的 JavaScript 表達式都是能夠的:正則表達式

let x = 1, y = 2;
let message = `<ul><li>${x}</li><li>${x + y}</li></ul>`;
console.log(message); // <ul><li>1</li><li>3</li></ul>
複製代碼

值得一提的是,模板字符串支持嵌套:express

let arr = [{value: 1}, {value: 2}];
let message = ` <ul> ${arr.map((item) => { return ` <li>${item.value}</li> ` })} </ul> `;
console.log(message);
複製代碼

打印結果以下:數組

string

注意,在 li 標籤中間多了一個逗號,這是由於當大括號中的值不是字符串時,會將其轉爲字符串,好比一個數組 [1, 2, 3] 就會被轉爲 1,2,3,逗號就是這樣產生的。異步

若是你要消除這個逗號,你能夠先 join 一下:函數

let arr = [{value: 1}, {value: 2}];
let message = ` <ul> ${arr.map((item) => { return ` <li>${item.value}</li> ` }).join('')} </ul> `;
console.log(message);
複製代碼

打印結果以下:優化

string

標籤模板

模板標籤是一個很是重要的能力,模板字符串能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串,舉個例子:ui

let x = 'Hi', y = 'Kevin';
var res = message`${x}, I am ${y}`;
console.log(res);
複製代碼

咱們能夠自定義 message 函數來處理返回的字符串:

// literals 文字
// 注意在這個例子中 literals 的第一個元素和最後一個元素都是空字符串
function message(literals, value1, value2) {
	console.log(literals); // [ "", ", I am ", "" ]
	console.log(value1); // Hi
	console.log(value2); // Kevin
}
複製代碼

咱們利用這些參數將其拼合回去:

function message(literals, ...values) {
	let result = '';

	for (let i = 0; i < values.length; i++) {
		result += literals[i];
		result += values[i];
	}

	result += literals[literals.length - 1];

	return result;
}
複製代碼

你也能夠這樣寫:

function message(literals, ...values) {
	let result = literals.reduce((prev, next, i) => {
	    let value = values[i - 1];
	    return prev + value + next;
	});

	return result;
}
複製代碼

學着拼合回去是一件很是重要的事情,由於咱們通過各類處理,最終都仍是要拼回去的……

oneLine

講完了基礎,咱們能夠來看一些實際的需求:

let message = ` Hi, Daisy! I am Kevin. `;
複製代碼

出於可讀性或者其餘緣由,我但願書寫的時候是換行的,可是最終輸出的字符是在一行,這就須要藉助模板標籤來實現了,咱們嘗試寫一個這樣的函數:

// oneLine 初版
function oneLine(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });

    result = result.replace(/(\s+)/g, " ");
    result = result.trim();

    return result;
}
複製代碼

實現原理很簡單,拼合回去而後將多個空白符如換行符、空格等替換成一個空格。

使用以下:

let message = oneLine ` Hi, Daisy! I am Kevin. `;
console.log(message); // Hi, Daisy! I am Kevin.
複製代碼

不過你再用下去就會發現一個問題,若是字符間就包括多個空格呢?舉個例子:

let message = oneLine` Preserve eg sentences. Double spaces within input lines. `;
複製代碼

若是使用這種匹配方式,sentences.Double 之間的兩個空格也會被替換成一個空格。

咱們能夠再優化一下,咱們想要的效果是將每行前面的多個空格替換成一個空格,其實應該匹配的是換行符以及換行符後面的多個空格,而後將其替換成一個空格,咱們能夠將正則改爲:

result = result.replace(/(\n\s*)/g, " ");
複製代碼

就能夠正確的匹配代碼。最終的代碼以下:

// oneLine 第二版
function oneLine(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });

    result = result.replace(/(\n\s*)/g, " ");
    result = result.trim();

    return result;
}
複製代碼

stripIndents

假設有這樣一段 HTML:

let html = ` <span>1<span> <span>2<span> <span>3<span> `;
複製代碼

爲了保持可讀性,我但願最終輸入的樣式爲:

<span>1<span>
<span>2<span>
<span>3<span>
複製代碼

其實就是匹配每行前面的空格,而後將其替換爲空字符串。

// stripIndents 初版
function stripIndents(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });


    result = result.replace(/\n[^\S\n]*/g, '\n');
    result = result.trim();

    return result;
}
複製代碼

最難的或許就是這個正則表達式了:

result = result.replace(/\n[^\S\n]*/g, '\n');
複製代碼

\S 表示匹配一個非空白字符

[^\S\n] 表示匹配非空白字符換行符以外的字符,其實也就是空白字符去除換行符

\n[^\S\n]* 表示匹配換行符以及換行符後的多個不包含換行符的空白字符

replace(/\n[^\S\n]*/g, '\n') 表示將一個換行符以及換行符後的多個不包含換行符的空白字符替換成一個換行符,其實也就是將換行符後面的空白字符消掉的意思

其實吧,不用寫的這麼麻煩,咱們還能夠這樣寫:

result = result.replace(/^[^\S\n]+/gm, '');
複製代碼

看似簡單了一點,之因此能這樣寫,是由於匹配模式的緣故,你會發現,此次除了匹配全局以外,此次咱們還匹配了多行,m 標誌用於指定多行輸入字符串時應該被視爲多個行,並且若是使用 m 標誌,^ 和 $ 匹配的開始或結束是輸入字符串中的每一行,而不是整個字符串的開始或結束。

[^\S\n] 表示匹配空白字符去除換行符

^[^\S\n]+ 表示匹配以去除換行符的空白字符爲開頭的一個或者多個字符

result.replace(/^[^\S\n]+/gm, '') 表示將每行開頭一個或多個去除換行符的空白字符替換成空字符串,也一樣達到了目的。

最終的代碼以下:

// stripIndents 第二版
function stripIndents(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });


    result = result.replace(/^[^\S\n]+/gm, '');
    result = result.trim();

    return result;
}
複製代碼

stripIndent

注意,此次的 stripIndent 相比上面一節的標題少了一個字母 s,而咱們想要實現的功能是:

let html = ` <ul> <li>1</li> <li>2</li> <li>3</li> <ul> `;
複製代碼

string

其實也就是去除第一行的換行以及每一行的部分縮進。

這個實現就稍微麻煩了一點,由於咱們要計算出每一行到底要去除多少個空白字符。

實現的思路以下:

  1. 使用 match 函數,匹配每一行的空白字符,獲得一個包含每一行空白字符的數組
  2. 數組遍歷比較,獲得最小的空白字符長度
  3. 構建一個正則表達式,而後每一行都替換掉最小長度的空白字符

實現的代碼以下:

let html = ` <ul> <li>1</li> <li>2</li> <li>3</li> <ul> `;


function stripIndent(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });

    const match = result.match(/^[^\S\n]*(?=\S)/gm);
    console.log(match); // Array [ " ", " ", " ", " ", " " ]

    const indent = match && Math.min(...match.map(el => el.length));
    console.log(indent); // 4

    if (indent) {
        const regexp = new RegExp(`^.{${indent}}`, 'gm');
        console.log(regexp); // /^.{4}/gm

        result =  result.replace(regexp, '');
    }

    result = result.trim();

    return result;
}
複製代碼

值得一提的是,咱們通常會覺得正則中 . 表示匹配任意字符,實際上是匹配除換行符以外的任何單個字符。

最終精簡的代碼以下:

function stripIndent(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {
        let expression = expressions[i - 1];
        return prev + expression + next;
    });

    const match = result.match(/^[^\S\n]*(?=\S)/gm);
    const indent = match && Math.min(...match.map(el => el.length));

    if (indent) {
        const regexp = new RegExp(`^.{${indent}}`, 'gm');
        result =  result.replace(regexp, '');
    }

    result = result.trim();

    return result;
}
複製代碼

includeArrays

前面咱們講到爲了不 ${} 表達式中返回一個數組,自動轉換會致使多個逗號的問題,須要每次都將數組最後再 join('') 一下,再看一遍例子:

let arr = [{value: 1}, {value: 2}];
let message = ` <ul> ${arr.map((item) => { return ` <li>${item.value}</li> ` }).join('')} </ul> `;
console.log(message);
複製代碼

利用標籤模板,咱們能夠輕鬆的解決這個問題:

function includeArrays(template, ...expressions) {
    let result = template.reduce((prev, next, i) => {

        let expression = expressions[i - 1];

        if (Array.isArray(expression)) {
            expression = expression.join('');
        }

        return prev + expression + next;
    });

    result = result.trim();

    return result;
}
複製代碼

最後

你會發現以上這些函數拼合的部分都是重複的,咱們徹底能夠將其封裝在一塊兒,根據不一樣的配置實現不能的功能。若是你想在項目中使用這些函數,能夠本身封裝一個或者直接使用 common-tags

ES6 系列

ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog。

ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級做用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索