模板字符串

什么是模板字符串

JavaScript 中,模板字符串是在 ECMAScript2015 中加入的新语法。
它以反引号(`)替代普通字符串的单引号、双引号,允许在内部使用特定语法(${...})嵌入表达式,同时它还支持多行字符串。

以上功能基本稍有了解的应该都知道并使用过了,但是模板字符串还有一个特性:带标签的模板字符串。

如果一个模板字符串前面是一个表达式(通常是一个函数),该字符串就被称为 带标签的模板字符串。它会在模板字符串处理后被调用。

基本语法

创建一个字符串变量

1
2
3
4
5
6
// ES6 之前的字符串
var single = 'this is a single-quote string';
var double = "this is a double-quote string";

// ES6 的模板字符串
var template = `this is a template string`;

如果要插入一个变量的话

1
2
3
4
5
// 普通字符串
var str1 = '1 + 2 = ' + (1 + 2);

// 模板字符串
var str2 = `1 + 2 = ${1 + 2}`;

如果要在字符串内换行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 普通字符串
var str3 = 'string line 1\n' +
'string line 2';
// string line 1
// string line 2

// 模板字符串
var str4 = `string line 1
string line 2`; // 需要注意的是这里最前面不加空格,如果加上空格或 tab 缩进,会出现在字符串中
// string line 1
// string line 2

// 如果加上空格
var str5 = `string line 1
string line 2`;
// string line 1
// string line 2

带标签的模板字符串

更高级形式的模板字符串是带标签的模板字符串。标签使得你可以用函数来解析模板字符串。标签函数的第一个参数是通过占位符分割后的字符串数组,剩余参数为占位符中表达式的返回值。你可以根据需要在函数中返回处理好的字符串,或者一个完全不同的数据类型:一个新的函数、对象、其他的字符串、布尔值……所有 JavaScript 支持的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var age = 16;
function myTag(templateStrings, age) {
var str0 = templateStrings[0];
var ageStr = '';
if (age < 18) {
ageStr = 'juvenile';
} else {
ageStr = 'adult';
}
return str0 + ageStr;
}

myTag`That guy is ${age}`; // "That guy is juvenile"


// 标签函数也可以返回其他的类型,比如返回一个新的函数
function template(strings, ...keys) {
return (function(...values) {
var dict = values[values.length - 1] || {};
var result = [strings[0]];
keys.forEach(function(key, i) {
var value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
});
}

var t1Closure = template`${0}${1}${0}!`;
t1Closure('Y', 'A'); // "YAY!"
var t2Closure = template`${0} ${'foo'}!`;
t2Closure('Hello', {foo: 'World'}); // "Hello World!"

模板字面量及转义序列

自 ES2016 开始,模板字面量遵循以下转义序列的规则:

  • Unicode 字符以 \u 开头,例如:\u00A9
  • Unicode 码位以 \u{} 表示,例如:\u{2F804}
  • 十六进制以 \x 开头,例如:\xA9
  • 八进制以 \ 和数字开头,例如:\251

据此规则,可以得出以下表达式是有问题的,因为对于每一个 ECMAScript 语法,解析器都会去查找有效的转义序列,但是只能得到这是一个形式错误的语法:

1
2
3
4
5
6
7
myTag`\unicode`;
// 在较老的ECMAScript版本中报错(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence
// 注:以上注释信息并非实测,而是在 MDN 上复制下来的
// 带标签的模板字面量在 ES2018 中移除了该转义序列的限制

`\unicode`; // SyntaxError: Invalid Unicode escape sequence

虽然在 ES2018 中移除了在带标签的模板字面量中对转义序列的语法限制,但是非法转义序列仍然可以在标签函数得到的参数中体现出来。

1
2
3
4
5
6
function latex(strings) {
return { cooked: strings[0], raw: strings.raw[0] }
}

latex`\unicode`;
// { cooked: undefined, raw: "\\unicode" }

由此我们又引出了一个新的属性:raw

原始字符串

标签函数的第一个参数中,存在着一个特殊的属性 raw,我们可以访问模板字符串中的原始字符串,而不经过特殊字符的替换。

1
2
3
4
5
6
7
8
9
10
function tag(strings) {
console.log(strings.raw);
}

var one = 1;
var two = 2;

tag`string line ${one} \n string line ${two}`
// ["string line ", " \n string line ", ""]
// 注:这里打印出的 "\n" 并非是换行符,而是字符串 "\\n"

另外,使用 String.raw() 方法创建原始字符串和使用默认模板函数和字符串连接创建是一样的。

1
2
3
4
var str = String.raw`Hey\nYang`;
str.length; // 9
str === 'Hey\nYang'; // false
str === 'Hey\\nYang'; // true

参考资料

  • 模板字符串 - JavaScript | MDN