ES6模板字符串完全指南:从基础语法到高级应用

引言

ES6(ECMAScript 2015)引入了模板字符串(Template Literals),彻底改变了 JavaScript 中字符串的拼接方式。相比传统的引号字符串,模板字符串提供了多行文本、字符串插值、标签模板等强大功能,使代码更加简洁易读。本文将全面介绍模板字符串的特性和应用场景。

基础语法

定义方式

模板字符串使用反引号(`)包裹,而非单引号或双引号。

1
2
3
4
5
6
7
// 传统字符串
var name = "John";
var greeting = "Hello, " + name + "!";

// 模板字符串
let name = "John";
let greeting = `Hello, ${name}!`;

字符串插值

使用 ${expression} 在字符串中嵌入表达式。

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
let user = {
name: "Alice",
age: 25
};

// 变量插值
let message = `用户 ${user.name} 今年 ${user.age} 岁`;
// 结果: "用户 Alice 今年 25 岁"

// 表达式插值
let price = 100;
let discount = 0.8;
let finalPrice = `折后价: ${price * discount} 元`;
// 结果: "折后价: 80 元"

// 函数调用插值
function formatMoney(amount) {
return ${amount.toFixed(2)}`;
}
let total = `总计: ${formatMoney(199.9)}`;
// 结果: "总计: ¥199.90"

// 三元运算插值
let isVip = true;
let level = `会员等级: ${isVip ? 'VIP' : '普通'}`;
// 结果: "会员等级: VIP"

多行字符串

模板字符串天然支持多行,无需使用 \n 或字符串拼接。

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
// 传统方式(痛苦)
var html = "<div>\n" +
" <h1>标题</h1>\n" +
" <p>内容</p>\n" +
"</div>";

// 模板字符串方式(简洁)
let html = `
<div>
<h1>标题</h1>
<p>内容</p>
</div>
`;

// 实际应用:SQL 语句
let userId = 123;
let query = `
SELECT
u.id,
u.name,
u.email,
o.order_count
FROM users u
LEFT JOIN (
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
) o ON u.id = o.user_id
WHERE u.id = ${userId}
`;

高级特性

嵌套模板

模板字符串可以嵌套使用,构建复杂的字符串结构。

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
let items = ['苹果', '香蕉', '橙子'];

let list = `
<ul>
${items.map(item => `
<li>${item}</li>
`).join('')}
</ul>
`;

// 结果:
// <ul>
// <li>苹果</li>
// <li>香蕉</li>
// <li>橙子</li>
// </ul>

// 条件渲染
let user = { name: 'Tom', vip: true };
let badge = `
<div class="user">
<span>${user.name}</span>
${user.vip ? `
<span class="vip-badge">VIP</span>
` : ''}
</div>
`;

原始字符串(String.raw)

String.raw 可以获取模板字符串的原始形式,不处理转义字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 普通模板字符串
let path = `C:\Windows\System32`;
console.log(path);
// 结果: "C:WindowsSystem32" (\被转义了)

// 使用 String.raw
let rawPath = String.raw`C:\Windows\System32`;
console.log(rawPath);
// 结果: "C:\Windows\System32" (保留原始形式)

// 实际应用:正则表达式
let regex = String.raw`\d+\.\d+`;
// 结果: "\d+\.\d+"
// 用于匹配小数如 "3.14"

// 文件路径处理
function createPath(...segments) {
return segments.join(String.raw`\`);
}
let fullPath = createPath('C:', 'Users', 'Admin', 'Documents');
// 结果: "C:\Users\Admin\Documents"

标签模板(Tagged Templates)

基础概念

标签模板是模板字符串最强大的特性,允许通过一个函数处理模板字符串。

1
2
3
4
5
6
7
8
// 语法
function tag(strings, ...values) {
// strings: 字符串数组
// values: 插值表达式的值
return processedString;
}

let result = tag`Hello ${name}, you have ${count} messages`;

参数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let name = "Alice";
let count = 5;

function myTag(strings, ...values) {
console.log(strings); // ["Hello ", ", you have ", " messages"]
console.log(values); // ["Alice", 5]

// 手动拼接
let result = "";
for (let i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) {
result += values[i];
}
}
return result;
}

let output = myTag`Hello ${name}, you have ${count} messages`;
// 结果: "Hello Alice, you have 5 messages"

实用标签函数

1. 自动转义 HTML

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
33
34
35
36
37
function htmlEscape(strings, ...values) {
const escapeMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
};

function escape(str) {
return String(str).replace(/[&<>"'/]/g, char => escapeMap[char]);
}

return strings.reduce((result, str, i) => {
return result + str + (values[i] ? escape(values[i]) : '');
}, '');
}

// 使用
let userInput = '<script>alert("xss")</script>';
let safeHtml = htmlEscape`<div>${userInput}</div>`;
// 结果: "<div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>"

// React JSX 风格的安全模板
function safeHTML(strings, ...values) {
const escaped = values.map(v =>
String(v)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
);

return strings.reduce((acc, str, i) =>
acc + str + (escaped[i] || ''), ''
);
}

2. 多语言国际化(i18n)

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
// 简单的国际化实现
const translations = {
'en': {
'HELLO': 'Hello {0}, you have {1} messages'
},
'zh': {
'HELLO': '你好 {0},你有 {1} 条消息'
}
};

function i18n(lang) {
return function(strings, ...values) {
// 使用第一个字符串作为 key
const key = strings.join('{}').toUpperCase();
const template = translations[lang][key] || key;

return template.replace(/\{(\d+)\}/g, (match, index) => {
return values[index] !== undefined ? values[index] : match;
});
};
}

// 使用
const t = i18n('zh');
let name = '张三';
let count = 3;
let message = t`hello ${name}, you have ${count} messages`;
// 结果: "你好 张三,你有 3 条消息"

3. 样式组件(CSS-in-JS)

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
33
function css(strings, ...values) {
const styles = strings.reduce((acc, str, i) => {
return acc + str + (values[i] || '');
}, '');

// 生成唯一类名
const className = `css-${hash(styles)}`;

// 插入样式表
const styleSheet = document.createElement('style');
styleSheet.textContent = `.${className} { ${styles} }`;
document.head.appendChild(styleSheet);

return className;
}

// 使用
let primaryColor = '#1890ff';
let buttonClass = css`
background-color: ${primaryColor};
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;

&:hover {
opacity: 0.8;
}
`;

// buttonClass: "css-a1b2c3"
// 生成的样式: .css-a1b2c3 { background-color: #1890ff; ... }

4. 日志格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function log(strings, ...values) {
const timestamp = new Date().toISOString();
const message = strings.reduce((acc, str, i) => {
return acc + str + (values[i] !== undefined ? values[i] : '');
}, '');

console.log(`[${timestamp}] ${message}`);
}

// 使用
let user = 'admin';
let action = 'login';
log`User ${user} performed ${action}`;
// 输出: [2024-01-15T10:30:00.000Z] User admin performed login

5. 查询参数构建

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
function query(strings, ...values) {
const params = [];
let queryText = strings.reduce((acc, str, i) => {
if (values[i] !== undefined) {
params.push(values[i]);
return acc + str + `$${params.length}`;
}
return acc + str;
}, '');

return { text: queryText, values: params };
}

// 使用(PostgreSQL 风格参数化查询)
let userId = 123;
let status = 'active';
let q = query`
SELECT * FROM users
WHERE id = ${userId}
AND status = ${status}
`;

// 结果:
// {
// text: "SELECT * FROM users WHERE id = $1 AND status = $2",
// values: [123, "active"]
// }

实际应用场景

1. URL 构建

1
2
3
4
5
6
7
8
9
10
11
12
function buildURL(base, ...pathSegments) {
const paths = pathSegments.map(p => encodeURIComponent(p));
return `${base}/${paths.join('/')}`;
}

// 使用
let apiBase = 'https://api.example.com';
let userId = 'user/123'; // 包含特殊字符
let resource = 'profile';

let url = buildURL(apiBase, 'v1', 'users', userId, resource);
// 结果: "https://api.example.com/v1/users/user%2F123/profile"

2. GraphQL 查询构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function gql(strings, ...values) {
const query = strings.reduce((acc, str, i) => {
return acc + str + JSON.stringify(values[i]);
}, '');

return { query };
}

// 使用
let userId = 123;
let fields = ['name', 'email', 'avatar'];

let query = gql`
query {
user(id: ${userId}) {
${fields.join('\n ')}
}
}
`;

3. 代码生成

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
33
34
35
36
37
38
39
40
41
function generateComponent(strings, ...values) {
const [name, props] = values;

return `
import React from 'react';

const ${name} = ({ ${props.join(', ')} }) => {
return (
${strings[1]}
);
};

export default ${name};
`.trim();
}

// 使用
let componentName = 'UserCard';
let propList = ['name', 'avatar', 'onClick'];
let template = generateComponent`
${componentName}
${propList}
<div className="user-card" onClick={onClick}>
<img src={avatar} alt={name} />
<span>{name}</span>
</div>
`;

// 生成的代码:
// import React from 'react';
//
// const UserCard = ({ name, avatar, onClick }) => {
// return (
// <div className="user-card" onClick={onClick}>
// <img src={avatar} alt={name} />
// <span>{name}</span>
// </div>
// );
// };
//
// export default UserCard;

与 Cocos Creator 结合

游戏内文本显示

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
33
34
35
36
37
38
// 动态生成游戏内提示
class GameText {
static format(template, params) {
return template.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match;
});
}

static showDamage(damage, isCritical) {
const color = isCritical ? '#FF0000' : '#FFFFFF';
const size = isCritical ? 48 : 32;

return {
text: `${damage}`,
style: `color: ${color}; font-size: ${size}px;`
};
}

static showLevelUp(level, rewards) {
let rewardText = rewards.map(r => `${r.name} x${r.count}`).join(', ');

return `
恭喜升级到 ${level} 级!
获得奖励:
${rewardText}
`.trim();
}
}

// 使用
let result = GameText.showLevelUp(10, [
{ name: '金币', count: 1000 },
{ name: '钻石', count: 50 }
]);
// 结果:
// "恭喜升级到 10 级!
// 获得奖励:
// 金币 x1000, 钻石 x50"

多语言支持

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
33
34
35
36
37
// 游戏多语言管理
class I18n {
constructor() {
this.locale = 'zh';
this.messages = {
'zh': {
'WELCOME': '欢迎回来,{username}',
'LEVEL_INFO': '当前等级: {level},经验: {exp}/{maxExp}'
},
'en': {
'WELCOME': 'Welcome back, {username}',
'LEVEL_INFO': 'Level: {level}, EXP: {exp}/{maxExp}'
}
};
}

t(key, params = {}) {
const template = this.messages[this.locale][key] || key;
return template.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match;
});
}
}

// 在 Cocos Creator 中使用
const i18n = new I18n();

// Label 组件
this.welcomeLabel.string = i18n.t('WELCOME', {
username: player.name
});

this.levelLabel.string = i18n.t('LEVEL_INFO', {
level: player.level,
exp: player.exp,
maxExp: player.maxExp
});

注意事项

1. 浏览器兼容性

1
2
3
4
5
6
7
8
9
// ES6 模板字符串现代浏览器原生支持
// IE 11 及以下不支持

// 如需兼容旧浏览器,使用 Babel 转译
// 转译前
let greeting = `Hello, ${name}!`;

// 转译后
var greeting = "Hello, " + name + "!";

2. 性能考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 模板字符串在运行时才求值
// 在性能敏感代码中注意使用

// 大量拼接时,数组 join 可能更快
function buildLargeString(items) {
// 模板字符串方式
return items.map(item => `Item: ${item}`).join('\n');

// 或数组方式(大数据量时可能更快)
const lines = [];
for (const item of items) {
lines.push('Item: ' + item);
}
return lines.join('\n');
}

3. 安全注意

1
2
3
4
5
6
7
8
// 模板字符串不会自动转义
let userInput = '<script>alert("xss")</script>';

// ❌ 危险:直接插入用户输入
let html = `<div>${userInput}</div>`;

// ✅ 安全:使用标签模板转义
let safeHtml = htmlEscape`<div>${userInput}</div>`;

总结

模板字符串是 ES6 引入的重要特性,它带来了:

特性 传统方式 模板字符串
字符串拼接 + 运算符 ${} 插值
多行文本 \n 或拼接 原生支持
HTML 转义 手动处理 标签模板
代码可读性 较差 优秀

最佳实践:

  • 优先使用模板字符串替代字符串拼接
  • 利用标签模板处理安全问题
  • 使用多行模板简化 HTML/XML 构建
  • 在 Cocos Creator 等游戏引擎中用于动态文本生成

掌握模板字符串,可以让 JavaScript 代码更加现代、简洁和安全。