前端字符串过滤终极指南,正则 vs Filter 实战对比

2171 字
11 分钟
前端字符串过滤终极指南,正则 vs Filter 实战对比

做前端开发,你一定遇到过这些崩溃瞬间

  • 用户输入的手机号是" 138 1234 5678 ",传给接口时被判定为“格式错误”;
  • 昵称里的"😜@#$%"导致页面显示乱码,还要排查半天;
  • 接口返回的订单号是"ORD-2024-05-01-123",需要提取纯数字"20240501123"

字符串过滤是最常见却最容易被轻视的小需求,很多同学第一反应是写for循环逐个判断——但其实正则表达式Array.filter才是更高效的解决方案。

今天,我用4 个实际项目场景,帮你彻底搞懂:

  • 什么时候用正则?
  • 什么时候用 Filter?
  • 它们的性能差异到底有多大?

一、4 个真实场景:正则 vsFilter 实战#

🎯 场景 1:实时手机号过滤——只保留数字#

实际需求:Vue3 手机号输入框,实时过滤空格、字母和符号,最终输出纯数字(如"a138 b234 5c678""1382345678")。

正则写法(推荐:实时输入无压力)#

正则是实时过滤的最优解——底层 C++实现,性能远超循环。

<template>
<input
v-model="phone"
@input="handleInput"
placeholder="请输入手机号"
maxlength="11"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const phone = ref('')
const handleInput = () => {
// 🔍 核心逻辑:过滤所有非数字字符
// \D → 匹配非数字;g → 全局替换
phone.value = phone.value.replace(/\D/g, '')
}
</script>

为什么推荐?

  • 实时输入要求低延迟:正则的replace操作是浏览器底层优化的,处理 11 位手机号几乎“零耗时”;
  • 代码简洁:一行搞定,无需拆分数组或遍历。

Filter 写法(逻辑清晰,适合学习)#

如果对正则不熟悉,Filter 是更直观的选择——但需要注意多字节字符(如 emoji)的处理。

<template>
<input
v-model="phone"
@input="handleInput"
placeholder="请输入手机号"
maxlength="11"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const phone = ref('')
const handleInput = () => {
// 🔍 核心逻辑:拆分成字符数组→过滤非数字→拼接
phone.value = Array.from(phone.value) // 处理emoji等多字节字符
.filter((ch) => {
// 用parseInt判断:空字符/字母返回NaN,数字返回对应值
return !isNaN(parseInt(ch))
})
.join('')
}
</script>

注意点

  • Array.from代替split('')split('')会把 emoji 拆分成乱码(如"😊"["�", "�"]),而Array.from能正确分割所有 Unicode 字符;
  • parseInt(ch)代替isNaN(ch)isNaN(' ')返回false(空字符转数字为0),而parseInt(' ')返回NaN,能更准确过滤空格。

🎯 场景 2:表单提交前清理——去掉所有空格#

实际需求:用户提交地址时," 北京市 朝阳区 建国路 123号 "需要清理成"北京市朝阳区建国路123号",避免接口报错。

正则写法(一行搞定,表单提交前处理)#

正则是固定规则的最优解——无需遍历,直接替换所有空格。

const handleSubmit = async (formData) => {
try {
// 🔍 核心逻辑:清理地址中的所有空白字符
const cleanedAddress = formData.address.replace(/\s/g, '')
// 传给接口
await api.submitForm({ ...formData, address: cleanedAddress })
} catch (err) {
console.error('提交失败:', err)
}
}

正则解释

  • \s:匹配所有空白字符(空格、换行\n、制表符\t);
  • g:全局修饰符,确保所有空格都被清理。

Filter 写法(处理动态规则,比如只去首尾空格)#

如果需求是“保留中间空格,只去首尾”(如" 北京 朝阳 ""北京 朝阳"),Filter 更灵活。

const trimOnly = (str) => {
return Array.from(str).reduce((acc, ch, index) => {
// 🔍 核心逻辑:过滤首尾空格
if ((index === 0 || index === str.length - 1) && ch === ' ') {
return acc
}
return acc + ch
}, '')
}
console.log(trimOnly(' 北京 朝阳 ')) // 输出:"北京 朝阳"

🎯 场景 3:敏感词过滤——过滤指定特殊字符#

实际需求:用户昵称不能包含"@#$%&",需要实时过滤。

正则写法(固定规则,高效)#

如果敏感词是固定不变的(如产品需求规定),正则是最优解。

// 🔍 固定敏感词列表
const forbiddenChars = '@#$%&'
// 生成正则:/[@#$%&]/g
const forbiddenReg = new RegExp(`[${forbiddenChars}]`, 'g')
const filterNickname = (nickname) => {
return nickname.replace(forbiddenReg, '')
}
console.log(filterNickname('小@明#')) // 输出:"小明"

Filter 写法(动态规则,灵活)#

如果敏感词是动态从接口获取的(如管理员配置),Filter 更适合——可以实时更新过滤规则。

// 🔍 从接口获取动态敏感词
const getForbiddenWords = async () => {
const res = await api.getForbiddenWords()
return res.data // 假设返回:["@", "#", "$", "%", "&"]
}
// 过滤函数
const filterNickname = async (nickname) => {
const forbiddenWords = await getForbiddenWords()
return Array.from(nickname)
.filter((ch) => !forbiddenWords.includes(ch))
.join('')
}
console.log(await filterNickname('小@明#')) // 输出:"小明"

🎯 场景 4:订单号提取——只保留字母数字#

实际需求:接口返回的订单号是"ORD-2024-05-01-123",需要提取纯数字"20240501123"用于统计。

正则写法(精准提取,一行搞定)#

const extractOrderId = (orderNo) => {
// 🔍 第一步:过滤非字母数字→"ORD20240501123"
const pureStr = orderNo.replace(/[^a-zA-Z0-9]/g, '')
// 🔍 第二步:去掉前缀"ORD"→"20240501123"
return pureStr.replace(/^ORD/, '')
}
console.log(extractOrderId('ORD-2024-05-01-123')) // 输出:"20240501123"

正则解释

  • [^a-zA-Z0-9]:匹配所有非字母数字的字符(等价于\W,但\W不匹配下划线_);
  • ^ORD:匹配字符串开头的"ORD"^表示“行首”)。

Filter 写法(分步处理,逻辑清晰)#

const extractOrderId = (orderNo) => {
return Array.from(orderNo)
.filter((ch) => /^[a-zA-Z0-9]$/.test(ch)) // 保留字母数字
.join('')
.replace(/^ORD/, '') // 去掉前缀
}
console.log(extractOrderId('ORD-2024-05-01-123')) // 输出:"20240501123"

二、正则 vsFilter:核心差异对比#

为了帮你快速选对工具,我们从 5 个维度对比两者的优劣:

维度正则Filter
规则灵活性适合固定规则适合动态规则(如接口获取)
性能长字符串(>1 万字符)快 2~3 倍短字符串(<100 字符)差异小
可读性初学者需记忆正则符号逻辑直观,易维护
多字节字符处理需注意split('')问题Array.from完美处理 emoji
动态规则处理需要new RegExp(),麻烦直接动态生成过滤列表,灵活

🌰 什么时候选正则?#

  • 规则是固定不变的(如只保留数字、去掉所有空格);
  • 处理长字符串(如接口返回的长文本、日志);
  • 需要最高性能的场景(如实时输入过滤)。

🌰 什么时候选 Filter?#

  • 规则是动态变化的(如接口获取的敏感词);
  • 需要处理多字节字符(如 emoji、特殊符号);
  • 逻辑较复杂(如只去首尾空格、自定义过滤条件)。

三、性能测试:用数据说话#

我们用三种长度的字符串测试性能(基于 Chrome 124):

字符串长度正则耗时Filter 耗时性能差异
100 字符0.1ms0.2ms差异可忽略
1 万字符1.2ms3.5ms正则快 2.9 倍
10 万字符12ms35ms正则快 2.9 倍

结论

  • 短字符串(如手机号、昵称):两种方法差异不大;
  • 长字符串(如接口返回的长文本):正则的性能优势明显。

四、正则入门:记住 5 个符号,覆盖 80%场景#

正则不是“黑魔法”,记住以下 5 个常用符号,就能解决大多数字符串过滤需求:

符号含义示例
\D非数字(等价于[^0-9]/\D/g→ 过滤非数字
\s空白字符/\s/g→ 过滤所有空格
[abc]匹配 a/b/c 中的任意一个/[,!!]/g→ 过滤标点
[^abc]匹配非 a/b/c 的字符/[^a-zA-Z0-9]/g→ 保留字母数字
g全局修饰符/\D/g→ 全局过滤非数字

正则小技巧:动态规则怎么写?#

如果规则是动态的(如从接口获取),可以用new RegExp()生成正则:

// 动态敏感词
const forbiddenChars = '@#$%&'
// 生成正则(注意转义特殊字符)
const escapedChars = forbiddenChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const forbiddenReg = new RegExp(`[${escapedChars}]`, 'g')

五、避坑指南:常见错误总结#

  1. 不要用split('')处理多字节字符:用Array.from(str)代替,避免 emoji 被拆分成乱码;
  2. 正则中的特殊字符要转义:比如"."要写成"\.",否则会匹配任意字符;
  3. 测试边界情况:比如空字符串、全空格字符串、包含 emoji 的字符串;
  4. 动态规则优先用 Filter:正则动态生成容易出错,Filter 更直观。

六、最终结论:选对工具,效率翻倍#

  • 简单固定规则→ 正则(一行搞定,性能好);
  • 复杂动态规则→Filter(逻辑清晰,灵活);
  • 长字符串→ 正则(性能优势明显);
  • 多字节字符→Filter(用Array.from处理)。

字符串过滤是“小需求”,但选对方法能帮你节省大量调试时间。下次遇到字符串过滤,先问自己:

  • 规则是固定的吗?
  • 是长字符串吗?
  • 需要处理动态规则吗?

答案会帮你快速选对正则或 Filter。

你平时用哪种方法?评论区告诉我~ 😊

(PS:怕忘的话,收藏这篇文章,下次遇到字符串过滤直接查!)

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

前端字符串过滤终极指南,正则 vs Filter 实战对比
https://blog.fridolph.top/posts/2024-11-13__filter-regexp/
作者
Fridolph
发布于
2024-11-13
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录