JavaScript 字符串与模板字符串

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Node.js 22 LTS

字符串处理几乎出现在每个脚本里:拼接输出、截取字段、清洗输入、生成模板。这一篇整理真正高频的方法。

三种引号

const a = "双引号";
const b = '单引号';
const c = `反引号(模板字符串)`;

前两者完全等价,用哪个取决于字符串里有没有引号字符:

const text = '他说:"你好"';   // 内部有双引号,外层用单引号

需要换行或多行文本时用模板字符串,普通引号里换行必须写 \n。

模板字符串

反引号里用 ${} 插入表达式:

const name = "ann";
const score = 95;
const msg = `${name} 的得分是 ${score}`;
console.log(msg);   // "ann 的得分是 95"

${} 里是完整的表达式,不是只能放变量:

`合计:${orders.filter((o) => o.paid).length} 笔`;
`${score >= 60 ? "及格" : "不及格"}`;
`${(price * 1.13).toFixed(2)} 元`;

多行文本直接换行,缩进会被保留:

const html = `
<ul>
  <li>${name}</li>
</ul>
`;

嵌套模板字符串要小心可读性,超过两层就该提取成变量。

字符串是不可变的

let s = "hello";
s[0] = "H";
console.log(s);   // "hello",没有任何变化

所有「修改」方法都返回新字符串,原字符串不变:

const s = "hello";
s.toUpperCase();       // "HELLO",但 s 仍是 "hello"
const upper = s.toUpperCase();

长度与索引

const s = "hello";
s.length;      // 5
s[0];          // "h"
s.at(-1);      // "o",支持负索引(ES2022)

length 统计的是 UTF-16 码元,不是字符数。带 emoji 或某些生僻字时会“不准”:

"😀".length;                 // 2,一个 emoji 占两个码元
[... "😀"].length;           // 1,展开后按码点统计

需要按「用户看到的一个字符」处理时,用展开 [...str] 或 Array.from(str) 转成数组。

查找与判断

const s = "hello world";

s.includes("world");    // true,是否包含
s.startsWith("he");     // true
s.endsWith("ld");       // true
s.indexOf("o");         // 4,首次出现位置,不存在返回 -1
s.lastIndexOf("o");     // 7,最后一次出现位置

判断存在用 includes(返回布尔),需要位置才用 indexOf。

截取:slice 与 substring

const s = "hello world";

s.slice(0, 5);     // "hello"(含头不含尾)
s.slice(6);        // "world"
s.slice(-5);       // "world",支持负索引
s.substring(0, 5); // "hello"

两者差别:slice 支持负索引,且 start > end 时返回空串;substring 会把负索引当 0,且自动交换大小顺序。统一用 slice 即可。

替换

const s = "a-b-c";

s.replace("-", "/");       // "a/b-c",只替换第一个
s.replace(/-/g, "/");      // "a/b/c",正则 + g 标志替换全部
s.replaceAll("-", "/");    // "a/b/c",字符串参数也替换全部(ES2021)

replace 的字符串参数只替换第一处,这是最常见的坑。要替换全部,用 replaceAll 或带 g 标志的正则。

替换值是字符串时,$& 等有 special 含义,需要字面替换时用函数形式:

s.replaceAll("-", () => "/");

拆分与拼接

"a,b,c".split(",");          // ["a", "b", "c"]
"a,b,c".split(",", 2);       // ["a", "b"],限制返回个数
["a", "b"].join("-");        // "a-b"
["a", "b"].join("");         // "ab"

split 与 join 互为逆操作,是处理 CSV 风格文本的基础工具。

去空白与补齐

"  hi  ".trim();          // "hi",去两端空白
"  hi  ".trimStart();     // "hi  "
"  hi  ".trimEnd();       // "  hi"
"7".padStart(3, "0");     // "007"
"7".padEnd(3, "-");       // "7--"

trim 去除的空白包括空格、制表符和换行符,处理用户输入时几乎是必做的第一步。

大小写与重复

"Hello".toLowerCase();   // "hello"
"Hello".toUpperCase();   // "HELLO"
"-".repeat(3);           // "---"

比较时统一大小写是常见做法:a.trim().toLowerCase() === b.trim().toLowerCase()。

一个完整例子:清洗用户输入

function normalizeTag(raw) {
  return raw
    .trim()
    .toLowerCase()
    .replaceAll(/\s+/g, "-")
    .replaceAll(/[^a-z0-9一-龥-]/g, "");
}

normalizeTag("  Hello World  ");   // "hello-world"
normalizeTag("Node.js 22");        // "nodejs22"

常见错误与注意事项

  1. replace("a", "b") 只替换第一个:用 replaceAll 或 /a/g。
  2. 依赖 length 当字符数:emoji 占两个码元,用 [...str].length。
  3. 用 s[0] = "H" 修改字符:字符串不可变,要生成新字符串。
  4. substring 传负索引:会被当成 0,用 slice 避免意外。
  5. 模板字符串里嵌套过多表达式:可读性差,提取成变量再插入。

小结

  • 模板字符串用 ${} 插入任意表达式,支持多行。
  • 字符串不可变,所有修改方法返回新字符串。
  • 判断存在用 includes,截取用 slice,替换全部用 replaceAll。
  • split / join 是处理分隔文本的基本组合。

下一步