前言:在前端面试中,“反转字符串”是一道出现率极高的“热身题”。
很多候选人觉得这题太简单,随手写一个 split().reverse().join() 就觉得稳了。但实际上,这道看似简单的题目,能从 API 广度、逻辑思维、编程范式、性能意识 四个维度,彻底摸清一个候选人的底色。
今天,我们不仅要写出代码,更要聊聊代码背后的“门道”。
一、 面试官的内心戏
当面试官让你反转一个字符串时,他到底在看什么?
- API 熟练度:你是否还在用老旧的写法?是否了解 ES6+ 的新特性?
- 逻辑构建能力:如果不让你用内置 API,你能否手写逻辑?
- 计算机基础:你是否理解递归?你是否知道递归在 JavaScript 中的潜在风险?
Level 1:API 熟练度流派 (一行代码)
这是最常见的解法,也是实际开发中最常用的。
1.1 基础版
JavaScript
function reverseStr(str) {
return str.split('').reverse().join('');
}
1.2 进阶版(加分项)
JavaScript
function reverseStr(str) {
// 使用 ES6 扩展运算符展开
return [...str].reverse().join('');
}
console.log(reverseStr('hello')); // olleh
为什么推荐用 [...str]?
面试官可能会追问:“这两个写法有什么区别?”
这时候你可以拿出杀手锏:Emoji 支持。
JavaScript 中的字符串是 UTF-16 编码,某些特殊字符(如 Emoji或生僻字)由两个码元(Surrogate Pairs)组成。
- split('') 会把一个 Emoji 劈成两半,导致乱码。
- [...str] 使用了字符串迭代器,能够智能识别码点,完整保留 Emoji。
codeJavaScript
const s = "🚀火箭";
console.log(s.split('').reverse().join('')); // "箭火" (乱码)
console.log([...s].reverse().join('')); // "箭火🚀" (完美)
Level 2:稳健的循环逻辑 (不依赖 API)
如果面试官限制:“不能使用 reverse 方法”,这时候考查的是你的逻辑基本功。
2.1 倒序遍历 (Old School)
这是最稳健、最朴实的写法,思路也比较简单,体现了你对数组/字符串索引的掌控。
JavaScript
function reverseStr(str) {
let reversed = '';
// 从最后一个字符开始,向前遍历
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
2.2 正序遍历 + 头插法 (Modern)
利用 for...of 语法,代码更优雅。核心在于拼接顺序:将新遍历到的字符,拼接到结果字符串的最前面。
JavaScript
function reverseStr(str) {
let reversed = '';
for (const char of str) {
// 注意顺序:新字符 + 旧结果
reversed = char + reversed;
}
return reversed;
}
Level 3:函数式编程流派 (Reduce)
如果你想展示自己对 Functional Programming (函数式编程) 的理解,可以用 reduce。
逻辑是累加器与当前值的操作。
JavaScript
function reverseStr(str) {
// 1. 转为数组
// 2. 利用 reduce 归并
return [...str].reduce((acc, cur) => {
// 核心:把当前字符(cur) 加在 累加器(acc) 的前面
return cur + acc;
}, '');
}
解析:
- acc (accumulator):累加器,保存之前拼接好的反转字符串。
- cur (current):当前遍历到的字符。
- cur + acc:实现了“头插法”的效果。
Level 4:递归的艺术与陷阱 (深度拷问)
这是区分“初级工程师”与“资深工程师”的分水岭。写出递归代码只是第一步,指出递归的缺陷才是王者操作。
4.1 递归逻辑
递归的核心在于:把“反转整个字符串”这个大问题,拆解为“反转除去第一个字符后的子串 + 第一个字符”这个小问题。
JavaScript
function reverseStr(str) {
// 1. 基线条件 (Base Case):字符串为空,停止递归
if (str === '') {
return '';
}
// 2. 递归步骤:
// substr(1): 截取从索引1到结尾的子串
// charAt(0): 获取第一个字符
// 逻辑:把第一个字符扔到最后去
return reverseStr(str.substr(1)) + str.charAt(0);
}
console.log(reverseStr('hello'));
// 执行过程:
// reverseStr('ello') + 'h'
// (reverseStr('llo') + 'e') + 'h'
// ...
4.2 为什么工程中慎用?(亮点分析)
如果你写完上面的代码,并主动补充以下分析,面试官会对你刮目相看:
- Stack Overflow (爆栈) 风险:
JavaScript 引擎(特别是 V8)在大部分场景下并不支持尾调用优化 (Tail Call Optimization) 。每递归一次,都会在调用栈中创建一个新的栈帧。如果字符串非常长(例如几万个字符),直接就会抛出 RangeError: Maximum call stack size exceeded。 - 内存开销大:
在 reverseStr(str.substr(1)) 过程中,JS 需要不断地开辟堆内存来创建新的子字符串(String 是不可变的),这会造成大量的内存分配和垃圾回收(GC)压力。
结论:递归写法虽然体现了“分而治之”的算法思想,但在处理字符串反转这类简单任务时,在 JS 中由于缺乏引擎优化,并不是性能最优解。
总结
一道简单的面试题,可以有四种层级的回答:
| 方案 | 关键词 | 适用场景 | 备注 |
|---|---|---|---|
| Level 1 | [...str].reverse() | 日常开发 | 首选,简洁且支持 Emoji |
| Level 2 | for...of | 面试/手写逻辑 | 性能好,逻辑清晰 |
| Level 3 | reduce | 函数式编程 | 展示对高阶函数的理解 |
| Level 4 | 递归 | 算法展示 | 慎用,有爆栈风险,适合理论分析 |
最后建议:
在面试时,建议先写出 Level 1(展示 API 广度)或 Level 2(展示逻辑),然后主动提及 Level 4 的递归思路及其缺陷。这种“知其然,更知其所以然”的态度,才是面试官最想要的。