前言
在开发中,我们几乎每天都在跟字符串打交道:验证用户输入、提取关键数字、转换命名风格、替换模板变量......如果只会用 indexOf + substring 暴力解决,代码会变得冗长且脆弱。
正则表达式 + 字符串方法才是高效、优雅的解决方案。
本文基于一道拼多多前端笔试题 (验证手机号)展开,逐步深入到 typeof 与 Object.prototype.toString.call 的差异、match vs exec、replace 的回调艺术,最终实现一个 {``{name}} 模板引擎。读完本文,你将彻底掌握 JS 字符串处理的利器。
一、正则表达式基础与手机号验证
1.1 正则表达式是什么?
正则表达式是描述字符模式的规则。它用单个字符或字符组合来定义匹配条件,常用于:
-
校验:输入是否合法(手机号、邮箱)
-
提取:从文本中抓取数字、链接等
-
替换:敏感词过滤、日期格式化
1.2 核心语法速览
| 符号 | 含义 | 示例 |
|---|---|---|
/abc/ |
字面量写法,包裹规则 | /1[3-9]\d{9}/ |
[] |
匹配其中任意一个字符 | [3-9] 匹配 3~9 的数字 |
{} |
前一个元素出现的次数 | \d{9} 匹配 9 个数字 |
\d |
等价于 [0-9] |
匹配一个数字 |
^ |
匹配字符串开始位置 | ^1 以 1 开头 |
$ |
匹配字符串结束位置 | \d$ 以数字结尾 |
\w |
字母、数字、下划线 | \w+ 匹配单词 |
() |
分组捕获,提取子匹配 | -(\w) 捕获连字符后的字母 |
1.3 拼多多手机号验证题
题目要求: 如何验证用户输入的手机号是正确的?
可答:
-
11 位数字
-
以 1 开头
-
第 2 位不可以是 1、0、2(即第二位只能是 3--9)
-
后面 9 位任意数字
正则设计:
-
^1→ 以 1 开头 -
[3-9]→ 第二位不能是 0、1、2,等价于3-9 -
\d{9}→ 后 9 位数字 -
$→ 结尾javascriptconst reg = /^1[3-9]\d{9}$/; // 测试 console.log(reg.test('13888888898')); // true console.log(reg.test('11234567890')); // false(第二位是1) console.log(reg.test('10234567890')); // false(第二位是0) console.log(reg.test('1888888888')); // false(只有10位)💡 永远不要相信用户的输入,前端校验只是第一道关,后端同样要严格验证。
二、JS 数据类型与类型检测陷阱
2.1 基本类型 vs 引用类型
JS 数据类型分两大家族:
类型 家族 例子 number基本 100,NaNstring基本 'hello'boolean基本 true,falseundefined基本 undefinednull基本(特殊) nullobject引用 {},[],/regex/,new Date()引用类型 存储在堆内存中,变量保存的是地址。比如正则对象
/\d+/也属于object类型。2.2
typeof的局限性typeof对基本类型判断尚可,但对引用类型只能返回"object",无法区分数组、正则、日期:javascriptconsole.log(typeof {}); // "object" console.log(typeof []); // "object" console.log(typeof /\d/); // "object" console.log(typeof null); // "object" ------ 著名的 JS bug2.3
Object.prototype.toString.call------ 精确类型检测每个对象内部都有一个
[[Class]]属性,Object.prototype.toString可以获取它:javascriptconsole.log(Object.prototype.toString.call(/\d/)); // "[object RegExp]" console.log(Object.prototype.toString.call([])); // "[object Array]" console.log(Object.prototype.toString.call(null)); // "[object Null]"推荐 :在需要精确判断类型时(如判断是否为正则对象),使用
Object.prototype.toString.call(x)。
三、
String.prototype.match------ 匹配提取字符串3.1 基本用法
match接收一个正则表达式,返回匹配结果数组或null。非全局模式(无
g) :返回第一个完整匹配及捕获组信息。全局模式(有
g) :返回所有匹配的子串数组,丢弃捕获组。3.2 提取字符串中的数字(经典案例)
javascriptconst str = '价格是100元,进价是80,赚了20'; const reg = /\d+/g; const numbers = str.match(reg); console.log(numbers); // ['100', '80', '20']如果没有
g修饰符,只会匹配第一个数字并返回详细对象:javascriptconst regNoG = /\d+/; console.log(str.match(regNoG)); // 输出: // ['100', index: 4, input: '价格是100元,进价是80,赚了20', groups: undefined]
四、
RegExp.prototype.exec与match的区别4.1
exec的逐次匹配特性exec是正则对象的方法,无论是否带g,它都返回一个匹配结果 (包括捕获组),并更新正则对象的lastIndex属性。通过循环可以遍历所有匹配。javascriptconst reg = /(\d+)/g; const str = 'price 100, cost 80'; let match; while ((match = reg.exec(str)) !== null) { console.log(`找到 ${match[0]},捕获组 ${match[1]},位置 ${match.index}`); } // 找到 100,捕获组 100,位置 6 // 找到 80,捕获组 80,位置 154.2 核心区别对照表
特性 match(带/g)exec(循环调用)返回值 所有匹配的子串数组 一个匹配详情对象(含捕获组) 捕获组 丢弃,只返回全匹配 保留 ,可通过 [1]、[2]访问索引信息 无 包含 index、input使用场景 简单提取所有匹配项 需要捕获组或位置信息时 经典案例:驼峰转换需要捕获连字符后的字母
五、
String.prototype.replace------ 强大的替换艺术5.1 基础替换与分组引用
replace可以用正则配合$1、$2等引用捕获组。javascriptlet str = 'hello-world'; // 将 -w 替换成 W(首字母大写) let result = str.replace(/-(\w)/, '-$1'.toUpperCase()); // 但这样写不对 // 正确写法: result = str.replace(/-(\w)/, (_, letter) => letter.toUpperCase()); console.log(result); // 'helloWorld'注意:这里用回调函数更灵活。
5.2 回调函数参数详解
javascriptstr.replace(regex, (match, p1, p2, ..., offset, string) => { // match: 本次匹配到的完整子串 // p1, p2... 捕获组匹配的内容 // offset: 匹配子串在原字符串中的索引 // string: 原字符串本身 return 替换后的内容; });经典驼峰转换:
javascriptconst toCamelCase = (str) => { return str.replace(/-(\w)/g, (match, letter) => { return letter.toUpperCase(); }); }; console.log(toCamelCase('hello-world-js')); // 'helloWorldJs'
六、综合实战:实现
{``{name}}模板引擎6.1 需求
实现一个
render(template, data)函数,将模板中的{``{key}}替换为data中对应的值。javascriptlet template = '我是{{name}},年龄{{age}},性别{{sex}}'; let person = { name: 'lgl', age: 17, sex: '男' }; console.log(render(template, person)); // 输出:'我是lgl,年龄17,性别男'6.2 方式一:
replace一次性替换(推荐)javascriptfunction render(template, data) { return template.replace(/\{\{(\w+)\}\}/g, (match, key) => { return data[key] || ''; }); }6.3 方式二:递归 +
exec(源自笔试题笔记)使用
exec手动匹配并逐步替换,每次只处理第一个{``{}},递归直到没有模板标记。javascriptfunction render(template, data) { const reg = /\{\{(\w+)\}\}/; const match = reg.exec(template); if (!match) return template; const key = match[1]; const value = data[key]; const newTemplate = template.replace(reg, value); return render(newTemplate, data); }递归方式展示了
exec对捕获组的逐步处理,但效率不如replace全局一次替换。实际开发推荐使用replace加全局正则。
七、总结
知识点 核心要点 正则语法 []、{}、^$、\d、分组()、修饰符g数据类型 基本类型栈存储,引用类型堆存储 类型检测 typeof粗糙,Object.prototype.toString.call精确match无 g:第一个匹配+捕获组;有g:所有匹配子串(无捕获组)exec每次返回匹配详情(含捕获组),循环调用可遍历全部 replace支持分组引用 $1和回调函数,回调参数丰富模板引擎 /\{\{(\w+)\}\}/g+replace是最简实现正则和字符串方法是前端工程师的"瑞士军刀",掌握它们能让代码更加简洁、健壮。希望本文能帮你彻底理清这些易混淆的概念,并在实际项目中灵活运用。
最后留一个小思考:如果模板中出现了嵌套
{``{}}或数组循环,该如何扩展这个模板引擎?欢迎在评论区交流讨论。
如果觉得有帮助,请点赞 👍 收藏 ⭐ 关注,更多硬核前端文章持续输出中!