ES6 对字符串做了大量增强,其中最重要的就是模板字符串。
模板字符串#
基本语法#
用反引号 ` 包裹:
const name = '张三'const greeting = `你好,${name}!`console.log(greeting) // 你好,张三!多行字符串#
// ES5 多行字符串var html = '<div>\n' + ' <h1>标题</h1>\n' + '</div>'
// ES6const html = `<div> <h1>标题</h1></div>`所有空格和换行都会保留:
const text = `第一行 第二行(前面有两个空格)第三行`console.log(text)// 第一行// 第二行(前面有两个空格)// 第三行表达式嵌入#
${} 中可以是任意 JavaScript 表达式:
const a = 10const b = 20
console.log(`${a} + ${b} = ${a + b}`) // 10 + 20 = 30
// 调用函数function getName() { return '张三'}console.log(`用户:${getName()}`) // 用户:张三
// 三元表达式const score = 85console.log(`成绩:${score >= 60 ? '及格' : '不及格'}`) // 成绩:及格
// 访问对象属性const user = { name: '张三', age: 25 }console.log(`${user.name},${user.age}岁`) // 张三,25岁嵌套模板#
const items = ['苹果', '香蕉', '橙子']
const list = `<ul> ${items.map((item) => `<li>${item}</li>`).join('\n ')}</ul>`
console.log(list)// <ul>// <li>苹果</li>// <li>香蕉</li>// <li>橙子</li>// </ul>转义#
在模板字符串中使用反引号或 ${:
const code = `使用 \`反引号\` 和 \${表达式}`console.log(code) // 使用 `反引号` 和 ${表达式}标签模板#
标签模板(Tagged Template)是模板字符串的高级用法,函数名后面直接跟模板字符串:
function tag(strings, ...values) { console.log(strings) // ['Hello ', '!'] console.log(values) // ['World']}
tag`Hello ${'World'}!`参数说明#
strings:模板字符串被${}分割后的字符串数组values:所有${}中表达式的值
const name = '张三'const age = 25
function myTag(strings, ...values) { console.log(strings) // ['用户', ',年龄', ''] console.log(values) // ['张三', 25] console.log(strings.raw) // 原始字符串数组}
myTag`用户${name},年龄${age}`实用场景:HTML 转义#
function safeHTML(strings, ...values) { const escape = (str) => String(str) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"')
return strings.reduce((result, str, i) => { const value = values[i - 1] return result + escape(value) + str })}
const userInput = '<script>alert("xss")</script>'const html = safeHTML`<div>${userInput}</div>`console.log(html)// <div><script>alert("xss")</script></div>实用场景:国际化#
const i18n = { 'zh-CN': { greeting: (name) => `你好,${name}!`, }, 'en-US': { greeting: (name) => `Hello, ${name}!`, },}
function t(strings, ...values) { const locale = 'zh-CN' // 假设从某处获取 const key = strings.join('$') const template = i18n[locale][key] return template ? template(...values) : strings.join('')}
// 需要更复杂的实现,这里只是示例思路实用场景:样式组件#
CSS-in-JS 库如 styled-components 就是用标签模板实现的:
// 简化版实现function styled(strings, ...values) { const css = strings.reduce((result, str, i) => { return result + str + (values[i] || '') }, '')
return function (props) { return { style: css, props, } }}
const Button = styled` background: blue; color: white; padding: 10px 20px;`实用场景:SQL 防注入#
function sql(strings, ...values) { const escaped = values.map((v) => { if (typeof v === 'string') { return `'${v.replace(/'/g, "''")}'` } return v })
return strings.reduce((query, str, i) => { return query + str + (escaped[i - 1] ?? '') })}
const name = "O'Brien"const query = sql`SELECT * FROM users WHERE name = ${name}`console.log(query)// SELECT * FROM users WHERE name = 'O''Brien'Unicode 支持#
码点表示法#
ES6 支持 \u{码点} 语法:
// ES5 只能表示 \u0000 到 \uFFFFconsole.log('\u0041') // Aconsole.log('\u20BB7') // 乱码,被解析为 \u20BB + 7
// ES6 大括号语法console.log('\u{20BB7}') // 𠮷console.log('\u{1F600}') // 😀字符串遍历器#
for...of 正确识别 32 位字符:
const text = '𠮷a'
// for 循环无法正确处理for (let i = 0; i < text.length; i++) { console.log(text[i]) // 乱码, 乱码, a}
// for...of 正确处理for (const char of text) { console.log(char) // 𠮷, a}
// 获取正确长度console.log([...text].length) // 2console.log(text.length) // 3codePointAt 和 fromCodePoint#
const s = '𠮷a'
// charCodeAt 无法正确处理s.charCodeAt(0).toString(16) // d842
// codePointAt 正确返回码点s.codePointAt(0).toString(16) // 20bb7
// 从码点创建字符String.fromCodePoint(0x20bb7) // 𠮷String.fromCodePoint(0x1f600) // 😀
// fromCharCode 无法处理大于 0xFFFF 的码点String.fromCharCode(0x20bb7) // 乱码normalize#
处理 Unicode 组合字符:
// 同一个字符可能有不同的表示方式const s1 = '\u01D1' // Ǐ(一个字符)const s2 = '\u004F\u030C' // O + ̌(两个字符组合)
console.log(s1 === s2) // falseconsole.log(s1.length, s2.length) // 1, 2
// normalize 统一表示console.log(s1.normalize() === s2.normalize()) // trueString.raw#
获取模板字符串的原始内容:
console.log(`line1\nline2`)// line1// line2
console.log(String.raw`line1\nline2`)// line1\nline2
// 实际应用:Windows 路径const path = String.raw`C:\Users\name\file.txt`console.log(path) // C:\Users\name\file.txt也可以作为函数调用:
String.raw({ raw: ['a', 'b', 'c'] }, 1, 2)// 'a1b2c'实战应用#
HTML 模板#
function render(data) { return ` <!DOCTYPE html> <html> <head> <title>${data.title}</title> </head> <body> <h1>${data.title}</h1> <ul> ${data.items.map((item) => `<li>${item}</li>`).join('')} </ul> </body> </html> `}
const html = render({ title: '商品列表', items: ['苹果', '香蕉', '橙子'],})日志格式化#
function log(level) { return function (strings, ...values) { const message = strings.reduce((acc, str, i) => { return acc + str + (values[i] ?? '') }, '')
const timestamp = new Date().toISOString() console.log(`[${timestamp}] [${level}] ${message}`) }}
const info = log('INFO')const error = log('ERROR')
const user = '张三'info`用户 ${user} 登录成功`// [2024-01-01T00:00:00.000Z] [INFO] 用户 张三 登录成功简单的模板引擎#
function template(str) { return function (data) { return str.replace(/\{\{(\w+)\}\}/g, (_, key) => { return data[key] ?? '' }) }}
const tpl = template('你好,{{name}}!你的余额是 {{balance}} 元。')console.log(tpl({ name: '张三', balance: 100 }))// 你好,张三!你的余额是 100 元。小结#
| 特性 | 语法 | 用途 |
|---|---|---|
| 模板字符串 | `${expr}` | 字符串插值、多行文本 |
| 标签模板 | fn`...` | 自定义字符串处理 |
| Unicode 支持 | \u{码点} | 完整 Unicode 表示 |
| String.raw | String.raw`...` | 获取原始字符串 |
模板字符串让字符串操作变得更加直观,标签模板则为 DSL(领域特定语言)提供了强大的基础设施。