JS的字符串的功能一直以来都特别有限,缺乏很多特性,比如不能直接表示多行字符串、字符串格式化、HTML转义等等。ES6通过模板字面量方式进行了填补,它通过全新的方法解决了这些问题。
基础用法
模板字面量使用反引号``
标识。
let s = `hello world`
console.log(s) // 'hello world'
// 如果字符串中想要包含反引号,需要使用反斜杠(\)转义
let s2 = `\`hello world\``
console.log(s2) // '`hello world`'
多行字符串
在ES6之前,要想表示多行字符串,开发者通常需要手动加入\n
换行符。
JavaScript有一个语法bug,在换行符后面添加反斜杠,可以承接下一行的代码,虽然也起到了换行的作用,但不建议这么做。
let s = 'hello \n'
+'world'
console.log(s)
// 'hello'
// 'world'
let s2 = 'hello \n\
world'
console.log(s2)
// 'hello'
// 'world'
使用模板字面量可以轻松创建多行字符串,反引号内的空白符会作为字符串的一部分。
let s = `hello
world`
console.log(s)
// 'hello'
// 'world'
let s1 = `hello
world`
console.log(s1)
// hello
// world
由于空白符会作为字符串的一部分,所以通过缩进对齐文本时,第一行可以留空,然后缩进后面的内容。
let s = `
<p>
<span>hello world</span>
</p>`.trim()
console.log(s)
// <p>
// <span>hello world</span>
// </p>
变量占位符
变量占位符允许将任何有效的JS表达式嵌入到模板字面量中,并将其结果输出为字符串的一部分。变量占位符用${}
表示。
let s = 'hello', n = 1.3;
function fn() {
return 'world'
}
console.log(`${s} ${fn()} ${Math.cell(n)}`)
// 'hello world 2'
模板字面量可以嵌入到另一个模板字面量内部
let s = 'world'
console.log(`hello ${`${s}!`}`)
// 'hello world!'
标签模板
标签模板可以说是模板字面量最强大的地方。标签指的是模板字面量第一个反引号前面标注的字符串,模板就是两个反引号之间的部分。
let s = tag`Hello world`
tag是应用到Hello world模板字面量上的标签
定义标签
标签可以是一个函数:函数的第一个参数是数组,包含了模板字面量中的原始字符串;其他参数是每一个占位符的解释值。
let name = 'Xiaowang', country = 'China'
let s = tag`My name is ${name}, I am from ${country}`
如果有一个tag函数做为模板字面量的标签,那么它会接收3个参数:
- 第一个参数是数组,包含了3个原始字符串,即['My name is', ', I am from', '']
- 第二个参数是变量name的解释值,即Xiaowang
- 第三个参数是变量country的解释值,即China
let name = 'Xiaowang', country = 'China'
let s = tag`My name is ${name}, I am from ${country}`
// 等价于
let s = tag(['My name is', ', I am from', ''], 'Xiaowang', 'China')
标签函数通常使用不定参数特性来定义占位符,从而简化数据处理的过程
function tag(literals, ...substitutions) {
// 返回一个字符串
}
下面定义一个tag标签函数,模拟模板字面量的默认行为。
function tag(literals, ...substitutions) {
let ret = ''
// 使用substitutions的数量进行循环,而不是literals的数量
for (let i = 0; i < substitutions.length; i++) {
ret += literals[i]
ret += substitutions[i]
}
// 添加literals的最后一个元素
ret += literals[literals.length - 1]
return ret
}
let name = 'Xiaowang', country = 'China'
let s = tag`My name is ${name}, I am from ${country}`
console.log(s) // 'My name is Xiaowang, I am from China'
...
可以把不定参数表示成数组。上面的示例通过遍历substitutions数组(substitutions的元素个数始终比literals少1),交替组合literals和substitutions中的元素,最后加上literals的最后一个元素,完成了模拟模板字面量的默认行为。
应用
应用一:过滤HTML字符串,防止用户输入恶意内容
function saferHTML(literals, ...substitutions) {
let ret = ''
for (let i = 0; i < substitutions.length; i++) {
let s = String(substitutions[i])
ret += literals[i]
ret += s.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
ret += literals[literals.length - 1]
return ret
}
let content = '<script>alert(1)</script>' // 用户输入
let msg = saferHTML`${content} is safe` // 安全处理
console.log(msg)
// <script>alert(1)</script> is safe
应用二:多语言转换(国际化处理)
function i18n(literals, ...substitutions) {
// 数据转换
}
let name = 'Xiaowang', country = 'China'
let ret = i18n`My name is ${name}, I am from ${country}!`
// 我的名字是小王,我来自中国
应用三:模板引擎
模板字符串本身并不能取代模板引擎,它没有条件判断和循环处理这些功能,但可以通过标签函数,自己添加这些功能
function tpl(literals, ...substitutions) {
// 通过匹配特殊字符处理数据
}
let ret = tpl`
<ul>
#for item in ${books}
<li><i>#{item.title}</i> by #{item.author}</li>
#end
</ul>`
raw()
String.raw()方法常作为模板字面量的处理函数,它的第一个参数应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。
String.raw({ raw: 'test' }, 0, 1, 2);// 't0e1s2t'
// 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);