最近在整理项目文档时,遇到一个让我血压飙升的问题------从微信、PDF 或网页复制过来的文本,总是夹杂着各种「不三不四」的字符。明明看起来是正常的英文和数字,但粘贴到代码里就报错,搜索也搜不到。
排查了半天,罪魁祸首居然是全角字符 。全角的 ABC 和半角的 ABC 看起来几乎一样,但在程序眼里完全是两个不同的东西。这种「视觉混淆」在开发中造成的坑,相信不少朋友都踩过。
痛点:全角字符的「隐形炸弹」
在中文技术社区混久了,你会发现全角/半角问题是个「经典老番」。无论是:
- 从微信聊天记录复制代码片段
- 从 PDF 文档提取文本
- 从 Word 粘贴到终端或编辑器
- 处理用户提交的表单数据
全角字符总能以各种姿势给你「惊喜」。最典型的场景是------用户从某个中文输入法切到英文模式时,标点符号还是全角的。于是 if (x > 5) 变成了 if(x>5),直接编译报错。
传统解决方案无非是:手动逐个替换(效率低到让人崩溃)、写个简单的正则替换(但边界情况多如牛毛)、或者用一些重量级工具(杀鸡用牛刀)。
为什么不用现成的库?
说实话,市面上确实有不少现成的转换库,比如 fullwidth-halfwidth、wcwidth 之类的。但在我的场景里,有几个考量:
- 体积问题:为了一个转换功能引入整个库,对轻量级工具来说有点「奢侈」
- 依赖管理:独立开发的小工具,能少一个依赖就少一个
- 可控性:自己实现,遇到特殊字符可以随时调整映射规则
于是,我决定用 AI 辅助开发一个纯前端、零依赖的转换工具。
和 AI 协作:从「能用」到「好用」
第一轮:给 AI 提需求
我用的方式是先给 AI 一个清晰的 prompt,描述需求和约束:
markdown
写一个纯 JavaScript 的全角半角转换函数,要求:
1. 全角转半角
2. 半角转全角
3. 处理全角空格 U+3000
4. 处理全角标点 U+FF01-U+FF5E
5. 特殊货币符号映射
6. 不要修改原字符串
7. 处理 null/undefined 输入
AI 第一次生成的代码其实已经能跑,核心逻辑就是 charCodeAt 判断 + 偏移量加减。但有几个问题:
javascript
// AI 第一版的问题代码
function toHalfWidth(str) {
if (!str) return str; // 问题:null 和 '' 都返回 null
return str.replace(/[\uFF01-\uFF5E\u3000]/g, function(char) {
return String.fromCharCode(char.charCodeAt(0) - 0xFEE0);
});
}
这里有个典型的「AI 陷阱」:if (!str) 会把空字符串和 null 混淆,而且用正则替换的方式虽然简洁,但对特殊字符(如全角货币符号)的处理不够灵活。
第二轮:调优边界情况
我告诉 AI 这几个问题后,它迭代出了第二版,改用逐字符遍历的方式,并且把特殊映射表单独抽出来:
javascript
var FWSpecial = {
0xFFE0: '¢', 0xFFE1: '£', 0xFFE2: '¬',
0xFFE3: '¯', 0xFFE4: '¦', 0xFFE5: '¥', 0xFFE6: '₩'
};
function toHalfWidth(str) {
if (str == null) return '';
var out = '';
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c === 0x3000) {
out += ' ';
} else if (c >= 0xFF01 && c <= 0xFF5E) {
out += String.fromCharCode(c - 0xFEE0);
} else if (FWSpecial[c]) {
out += FWSpecial[c];
} else {
out += str.charAt(i);
}
}
return out;
}
这一版明显靠谱多了,但 AI 的「过度设计」倾向也暴露了------它加了很多不必要的类型检查,比如 typeof str !== 'string' 之类的防御代码。对于这个场景,str == null 的判断已经足够了。
第三轮:处理「看不见」的坑
真正的坑在测试阶段才暴露出来。我拿了一段从微信复制的文本测试,发现全角空格 U+3000 被正确转换了,但全角逗号 , 和全角句号 。 却原样保留。
原因是:微信复制出来的文本中,全角标点其实是 Unicode 中的「CJK 标点符号」区块(U+3001-U+303F),而不是全角 ASCII 区块(U+FF01-U+FF5E)。这俩完全是两回事。
我查了一下资料,发现这是个「历史遗留问题」------Unicode 在设计时把「全角 ASCII 变体」和「CJK 专用标点」分开了。于是跟 AI 讨论后,决定保持现状:只处理 U+FF01-U+FF5E 和 U+3000,CJK 标点保留原样。
这也是个很重要的设计决策:不要过度转换 。如果把 , 转成 ,,反而可能破坏中文排版的美感。
技术实现细节
最终的核心逻辑其实很简单,就是一个映射表 + 遍历:
javascript
function toHalfWidth(str) {
if (str == null) return '';
var out = '';
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c === 0x3000) {
out += ' '; // 全角空格
} else if (c >= 0xFF01 && c <= 0xFF5E) {
out += String.fromCharCode(c - 0xFEE0); // 全角 ASCII 变体
} else if (FWSpecial[c]) {
out += FWSpecial[c]; // 特殊货币符号
} else {
out += str.charAt(i); // 其他字符原样保留
}
}
return out;
}
反向转换 toFullWidth 就是反过来,把 0x21-0x7E 加上 0xFEE0 偏移。
这里有个有意思的点:为什么偏移量是 0xFEE0?因为 0xFF01 - 0x21 = 0xFEE0。全角字符和半角字符在 Unicode 里就差这个偏移量。
和 AI 协作的心得
这次开发让我对 AI 辅助编程有了新的认识:
- AI 擅长「骨架」,不擅长「边界」:它能快速生成核心逻辑,但边界情况(null、特殊字符、浏览器兼容性)往往需要人工补充
- prompt 要具体:给 AI 的描述越清晰,生成的代码质量越高。比如明确写出「处理 U+3000 全角空格」比「处理全角字符」效果好得多
- 测试驱动对话:拿真实场景的文本去测试 AI 生成的代码,把失败案例反馈给它,迭代速度会很快
- AI 会「过度设计」:有时候它会加一些不必要的防御代码,或者过度封装。这时候需要自己判断哪些是必要的
适用场景
这个工具最适合的场景:
- 数据处理:清洗从各种渠道复制来的文本
- 代码修复:处理用户输入中的全角字符
- 文本规范化:统一文档中的字符格式
- SEO 优化:避免因全角字符导致的搜索匹配问题
一点小思考
全角/半角问题看似简单,但背后涉及 Unicode 编码、字符映射、中文排版等多个领域的知识。这也是为什么我决定自己实现而不是直接用库------在解决「简单」问题的过程中,能学到不少「不简单」的细节。
而且,纯前端实现还有个好处:数据完全在本地处理,不会上传到服务器。对于处理敏感文本的场景,这算是个「隐形加分项」。
如果你也经常被全角字符困扰,可以在线体验这个工具:craftvo.app/zh/tool/ful...
最后说一句:在 AI 辅助编程的时代,「会用 AI」和「懂原理」并不矛盾。恰恰是那些你理解原理的地方,才能判断 AI 生成的代码是否靠谱。这波操作,属于是「AI 教我的,但我也教了 AI」了。