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,对做者也是一种鼓励。

相关文章
相关标签/搜索