JavaScript字符串拼接5种方法详解:原理、性能与坑点
发布时间:2026/9/29 1:22:40来源:尧图网络
字符串拼接这个话题说基础它是真的基础每个写 JavaScript 的人都用过但说简单它又不简单——同一个功能不同人写出来的代码差距可以非常悬殊。我在 code review 里见过直接把几十个变量用加号连成一条长龙的写法也见过老代码里为了拼一行 SQL 反复新建临时变量的典型案例每次都忍不住想把这块讲清楚JS 里到底有哪几种字符串拼接方法各自适合什么场景踩过哪些坑。这篇文章就围绕 JS 字符串拼接这件事把我这些年一直在用的 5 种方式一次性说透包括运算符、模板字符串、concat()、join()以及封装好的占位符替换把原理、性能、坑点都放到一起讲。不管是刚入门的新手还是写了两三年 JS 想优化代码风格的同学这 5 种方法都值得重新过一遍。1. 最直觉的加号运算和1.1 加号在 JS 中到底怎么工作在 JavaScript 里是个比较特殊的运算符它同时承担了“数字加法”和“字符串连接”两种职责。具体走哪条路取决于两边的操作数类型如果两边都是数字就走加法逻辑如果其中任意一边是字符串JS 引擎就会尝试把另一边也转成字符串然后进行拼接。console.log(1 2); // 3 console.log(1 2); // 12 console.log(1 2); // 12 console.log( 1); // 1这个行为可以用一句话概括只要有一边是字符串结果就是字符串。很直觉对吧但这也是很多隐性 bug 的源头。比如表单里拿到的值经常是字符串用户输入18你直接用inputValue 1结果不是19而是181。这种问题在实际开发中出现的频率高得惊人每次排查到最后都发现是类型转换在捣乱。还有一种常见写法是本质上是str str xxx的简写形式。let title 前端; title 入门; title 指南; console.log(title); // 前端入门指南1.2 加号拼接最常见的几个坑加号虽然简单但有几个点需要格外留意。第一个坑是运算顺序。和之间是左结合也就是说从左往右依次算。如果你在一个表达式中混用了字符串拼接和数字加法结果可能会出乎意料console.log(1 2 3); // 33 先算 123再拼 3 console.log(1 2 3); // 123 先拼 12再拼 3第二个坑是真值和假值的处理。用加号拼接null、undefined、NaN这些特殊值时它们会先被转成对应的字符串文本比如null、undefined、NaN这在日志输出和接口对接时很容易造成误解const user null; console.log(当前用户是 user); // 当前用户是null第三个坑是循环里拼大字符串。老式写法喜欢这样let result ; for (let i 0; i 10000; i) { result item i ,; }字符串在 JS 中是不可变的每次都会创建一个全新的字符串对象。循环 10000 次就会产生 10000 个中间字符串虽然现代引擎做了不少优化但大循环里依然可能造成明显的性能损耗和 GC 压力。这个问题的替代方案后面会用join()来解。1.3 什么时候该用加号加号适合的场景非常明确临时拼一两个字符串代码量少、逻辑直白比如拼个提示语、拼个简单的 URL 参数。它不需要额外学习成本任何人一眼就能看懂。代码里用加号本身不丢人过度封装反而会让简单的事情变复杂。只是不要让它出现在高频循环里也不要让它承担太复杂的格式化逻辑。2. 现代开发的主力模板字符串2.1 模板字符串为什么比加号好用ES6 引入的模板字符串用一对反引号包裹内容通过${}嵌入变量或表达式直接解决了加号拼接在“可读性”和“多行文本”上的不足。const name 张三; const age 25; const message 我叫${name}今年${age}岁; console.log(message);它最大的优势是可以直接放任意 JavaScript 表达式而不只是变量const price 99.5; const count 3; console.log(商品总价${price * count} 元); // 商品总价298.5 元 console.log(满减后${price * count 300 ? 减50 : 不减}); // 可以直接写三元模板字符串还支持多行文本这对拼接 HTML 片段、SQL、纯文本邮件特别友好。以前用加号换行要手动加\n和一堆加号现在反引号一包换行就是换行const html div classcard h3${title}/h3 p${description}/p /div ;这在渲染前端模板、拼接表格行时非常实用代码结构清晰不会再出现一行加号后面跟着\续行符的“蜈蚣代码”。2.2 模板字符串的高级玩法标签模板模板字符串还有一个进阶用法叫标签模板tagged template本质上是一个函数调用。你可以在反引号前面放一个函数名这个函数会接收到字符串片段和所有插值表达式的结果然后你可以在函数内部自定义如何处理它们。function highlight(strings, ...values) { return strings.reduce((result, str, i) { return result str (values[i] ? mark${values[i]}/mark : ); }, ); } const name 李四; const result highlight用户${name} 已登录; console.log(result); // 用户mark李四/mark 已登录这个能力在实现富文本高亮、多语言模板、日志脱敏时非常有用。不过对大多数业务代码来说直接用普通模板字符串就够了标签模板属于锦上添花的技巧。2.3 模板字符串的防坑要点模板字符串确实好用但有几个细节容易翻车。第一是${}内部如果还包含反引号需要转义或者换一种写法// 错误会提前结束模板字符串 // const bad 这是一段 code 示例; // 正确 const good 这是一段 \code\ 示例;第二是模板字符串里的缩进和空格会被原样保留。如果你想让多行模板的输出不带着乱七八糟的缩进可以配合trim()或replace处理。比如上面 HTML 的例子直接输出会发现每行行首都带着代码里的缩进空格很多人刚用时不习惯。第三是兼容性。模板字符串是 ES6 语法在旧版浏览器或不支持转译的环境里会直接报语法错误。现在的工程环境基本都有 Babel 处理问题不大但如果写纯脚本给老环境跑还是要确认一下。3. 存在感不高但能打的concat()方法3.1concat()的用法与冷知识String.prototype.concat()是字符串原型上自带的方法功能是把一个或多个字符串拼接到原字符串后面返回新字符串。const a Hello; const b a.concat( , World, !); console.log(b); // Hello World! console.log(a); // Hello 原字符串不变这个方法接受任意多个参数会按顺序拼接。和一样如果参数不是字符串会先转为字符串再拼const txt 数量.concat(12, 个); console.log(txt); // 数量12 个这里有一个冷知识concat()遇到null和undefined时会把它转成字符串null和undefined而不是给出空字符串或者报错。所以如果你不小心把一个可能为空的变量传进去了输出里会出现莫名其妙的null字样。3.2 为什么concat()平时用得少说实话我在实际项目中很少看到有人用concat()拼字符串原因主要是三个。第一运算符的可读性更直观。a b比a.concat(b)写起来更短读起来也更像“拼接”这个动作。第二ESLint 的prefer-template规则默认会建议用模板字符串替代字符串拼接concat()既不推荐也不禁止属于“可以用但没必要”的中间地带。第三性能上concat()并没有碾压在多数引擎里两者差距微乎其微。不过它有一个独特的使用场景当你需要延迟决定拼接内容时concat()比更容易做链式调用。比如在构建 HTML 类名时function buildClassName(base, ...modifiers) { return base .concat( ) .concat(modifiers.filter(Boolean).join( )); }这种写法虽然少见但代码意图非常明确就是“追加”。如果在团队里约定好用它处理“不断追加”的语义也能避免带来的隐式转换问题。4. 数组思路的拼接join()方法4.1join()的正确打开方式Array.prototype.join()严格来说是数组的方法不是字符串的方法。它会把数组里的所有元素转为字符串然后用你指定的分隔符连接起来最终返回一个字符串。这个思路和前面几种完全不同先收集片段再一次性拼接。const arr [2026, 01, 01]; console.log(arr.join(-)); // 2026-01-01 const list [苹果, 香蕉, 橙子]; console.log(list.join(、)); // 苹果、香蕉、橙子4.2 用join()解决真实项目里的拼接痛点join()最典型的场景是把动态的数据列表转成字符串。比如后端返回了一个标签数组前端要渲染成带分隔符的字符串const tags [JavaScript, TypeScript, Node.js]; const tagText tags.map(tag #${tag}).join( ); console.log(tagText); // #JavaScript #TypeScript #Node.js再比如拼 CSS 类名、拼 URL 参数、拼 CSV 行都是它的主场。还有一个非常实用的场景就是解决前面提到的“循环里拼接性能差”的问题。正确的姿势是把字符串片段丢进数组循环结束后一次性join()出来const items []; for (let i 0; i 10000; i) { items.push(item${i}); } const result items.join(,);这样做的好处是循环过程中只有数组在增长不会反复创建大字符串最终只做一次拼接操作。从内存角度来说比稳得多。4.3join()的边界行为与注意事项join()有几个容易被忽略的边界行为。如果没有传分隔符默认使用逗号,很多人会误以为默认是无分隔拼接。比如[a, b].join()返回的是a,b而不是ab。如果要无分隔拼接需要显式传空字符串[a, b].join()。空数组join()后返回空字符串只有一个元素的数组不会加分隔符。这两点在做判断时用起来很顺手比如判断arr.join().length 0就不用再单独判断数组长度。还有一点如果数组元素中包含null或undefinedjoin()会把它们当作空字符串处理而不会变成null或undefined。这和String()转换的行为不一样很多人在数据清洗时并不知道这个差异。5. 隐藏的第五种占位符替换与模板格式化5.1 一个通用的format函数前面四种是语言本身提供的能力第五种更像是一种模式自己封装一个字符串格式化函数通过占位符替换实现拼接。这种做法在复杂模板场景下非常有用也让字符串拼接从“无脑连接”升级为“结构化渲染”。一个经典实现是{name}风格占位符function format(template, data) { return template.replace(/\{(\w)\}/g, (match, key) { return data[key] ! undefined ? data[key] : match; }); } const text format(您好{name}您本月的账单金额是 {amount} 元。, { name: 王五, amount: 1299 }); console.log(text); // 您好王五您本月的账单金额是 1299 元。如果你更喜欢类 C 语言的%s风格也可以封装成这样function sprintf(str, ...args) { return str.replace(/%s/g, () args.shift()); } console.log(sprintf(%s 的博客发布了新文章%s, 前端工程师, JS 字符串拼接指南));5.2 为什么需要自己封装格式化函数你可能会问模板字符串都那么强了为什么还要自己写格式化函数这个问题问得好。模板字符串适合“写代码时就确定好拼接结构”的场景而占位符替换适合“拼接模板在运行时才确定”的场景。典型例子包括多语言文案{name} 已登录在不同语言下占位符位置不同不能写死在代码里。日志模板日志系统统一维护模板参数动态传进来。邮件/短信通知后台配置的通知文案需要动态替换变量。饼图标题、报表列名等带参数的业务文案。在这些场景中模板是动态数据不是代码里的常量用模板字符串反而很难处理。封装一个format函数把模板和参数分离可维护性会好很多。5.3 补充一个容易被忽略的工具String.raw在字符串拼接相关的工具里String.raw是个比较冷门但很好用的帮手。它是模板字符串的标签函数会返回“原始字符串”也就是反斜杠不会被解释成转义字符。const path String.rawC:\Users\admin\Desktop; console.log(path); // C:\Users\admin\Desktop如果直接用普通字符串\U、\a这类写法会被当成转义字符或者直接报错。在 Windows 路径拼接、正则表达式拼接等场景下String.raw能省去双反斜杠的烦恼。6. 选型策略与性能实测哪种方法更快更稳6.1 五种方法全方位对比为了帮你快速决策我把这 5 种方法从“可读性”“性能”“多行支持”“表达式支持”“适用场景”五个维度做了整理方法可读性多行支持表达式支持典型场景性能注意点/中不支持需先算好变量简单拼接、提示语大循环中会产生中间字符串模板字符串高支持支持任意表达式日常开发首选、HTML 拼接性能优秀引擎优化成熟concat()中低不支持参数先求值链式追加、多参数拼接与相当无明显优势join()中不支持需先构造数组数据列表、循环拼接、路径批量拼接效率高format()封装高支持依赖实现国际化、日志模板、文案渲染多一次正则开销可忽略6.2 我亲手跑过的一次简单拼接耗时对比为了不纸上谈兵我在 Node.js 环境下对几种方案做了个简单的压力测试循环 10 万次拼一段包含用户名和年龄的字符串结果方向基本符合预期。const user { name: 张三, age: 30 }; let str ; console.time(plus); for (let i 0; i 100000; i) { str 用户 user.name 年龄 user.age; } console.timeEnd(plus);如果分别换成模板字符串、数组pushjoin()多次测试下来会发现两个结论第一模板字符串和在简单场景下差距很小V8 引擎早就做过了“字符串连接”优化不用太纠结谁快谁慢。第二批量拼接场景循环里拼多个元素确实是join()更稳因为中间字符串更少。我个人的结论是性能不是选型的第一依据可读性和维护性才是。真正需要关注性能的场景是那些高频执行的字符串处理比如一秒钟跑几百次的日志格式化、报表生成、大文件解析这种才值得用pushjoin()或者更精细的方案。6.3 我的选型建议根据不同的业务场景我一般按下面的思路选两个或三个字符串临时拼一下直接用别纠结。字符串里有变量、有多行、有表达式首选模板字符串。把一个数组转成带分隔符的字符串直接join()。循环里拼接大量片段先push进数组最后join()。模板内容来自配置或接口用自己封装的format()函数。涉及 Windows 路径或正则的原始字符串用String.raw。这套规则简单直接团队成员也好记。7. 常见问题与排查技巧实录7.1 高频坑点速查表字符串拼接的错误通常不是编译期报错而是运行期结果不对。这里把常见的坑整理成表方便你在排错时对照。现象原因解决方案1 1结果是11字符串优先触发拼接数字被转字符串先Number()转数字再相加输出中莫名出现null变量为null时被拼接进字符串拼接前用??或 模板字符串多行输出带多余空格代码缩进被保留配合trim()或写在一行[a, b].join()返回a,bjoin()默认分隔符是逗号显式传空字符串join()反引号模板里想再包反引号反引号需要转义使用 或嵌套用不同引号数字保留小数位拼接后位数不对number.toFixed()返回字符串又被拼接把toFixed()结果参与计算后再拼7.2 字符串包含判断拼接之外最常用的字符串操作在拼接字符串的日常工作中判断一个字符串是否包含某个子串同样高频。很多初学者分不清includes()和indexOf()的取舍这里顺手整理一下。const url https://example.com/api/users; console.log(url.includes(users)); // true推荐 console.log(url.indexOf(users) ! -1); // true老写法 console.log(url.startsWith(https)); // true console.log(url.endsWith(/users)); // true console.log(url.search(/api\/\w/)); // 返回索引或 -1 console.log(url.match(/api\/\w/)); // 返回匹配数组或 nullincludes()语义最直白返回布尔值适合大多数判断场景。indexOf()适合需要拿索引位置的场景。search()和match()是正则派适合复杂模式。做字符串包含判断时不要用正则去匹配固定字符串杀鸡用牛刀还容易写错转义。7.3 排查字符串拼接问题的一个实用流程如果你拼接出来的结果不对别急着改代码按下面的顺序查一遍基本能定位 90% 的问题。先检查变量类型。用typeof看看参与拼接的变量到底是什么类型尤其注意接口返回的数据到底是number还是string最好在拼接前打一条日志。再看是否有null或undefined混入。这类值拼接后会变成字符串null或undefined非常隐蔽。然后看隐式转换是否符合预期。必要的时候显式调用String()或toString()统一格式别依赖${}的自动转换。最后检查分隔符和换行。多行模板里的缩进、join()默认的逗号都是容易看漏的地方。把这些过一遍基本能解决绝大多数拼接异常。我在实际开发中养成了一个习惯小拼接用日常拼装用模板字符串遇到“把一批数据变成字符串”的需求就想起join()有模板化诉求就封装独立的格式化函数。最后再分享一个多年踩坑之后沉淀的小技巧日志系统里如果需要不断追加内容不要一直str str xxx而是把片段推进数组最后join()一次。字符串不可变性决定了这种写法在极端场景下能省下不少内存。字符串拼接看着简单但用对了方法和场景代码质量真的会肉眼可见地变好希望这 5 种方法能让你少一点纠结多一点从容。
网站建设高端定制企业官网