从拼多多手机号验证到模板引擎:深入正则表达式与 JS 字符串处理

前言

在开发中,我们几乎每天都在跟字符串打交道:验证用户输入、提取关键数字、转换命名风格、替换模板变量......如果只会用 indexOf + substring 暴力解决,代码会变得冗长且脆弱。

正则表达式 + 字符串方法才是高效、优雅的解决方案。

本文基于一道拼多多前端笔试题 (验证手机号)展开,逐步深入到 typeofObject.prototype.toString.call 的差异、match vs execreplace 的回调艺术,最终实现一个 {``{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 拼多多手机号验证题

题目要求: 如何验证用户输入的手机号是正确的?

可答:

  1. 11 位数字

  2. 以 1 开头

  3. 第 2 位不可以是 1、0、2(即第二位只能是 3--9)

  4. 后面 9 位任意数字

正则设计

  • ^1 → 以 1 开头

  • [3-9] → 第二位不能是 0、1、2,等价于 3-9

  • \d{9} → 后 9 位数字

  • $ → 结尾

    javascript 复制代码
    const 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, NaN
    string 基本 'hello'
    boolean 基本 true, false
    undefined 基本 undefined
    null 基本(特殊) null
    object 引用 {}, [], /regex/, new Date()

    引用类型 存储在堆内存中,变量保存的是地址。比如正则对象 /\d+/ 也属于 object 类型。

    2.2 typeof 的局限性

    typeof 对基本类型判断尚可,但对引用类型只能返回 "object",无法区分数组、正则、日期:

    javascript 复制代码
    console.log(typeof {});         // "object"
    console.log(typeof []);         // "object"
    console.log(typeof /\d/);       // "object"
    console.log(typeof null);       // "object" ------ 著名的 JS bug

    2.3 Object.prototype.toString.call ------ 精确类型检测

    每个对象内部都有一个 [[Class]] 属性,Object.prototype.toString 可以获取它:

    javascript 复制代码
    console.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 提取字符串中的数字(经典案例)

    javascript 复制代码
    const str = '价格是100元,进价是80,赚了20';
    const reg = /\d+/g;
    const numbers = str.match(reg);
    console.log(numbers); // ['100', '80', '20']

    如果没有 g 修饰符,只会匹配第一个数字并返回详细对象:

    javascript 复制代码
    const regNoG = /\d+/;
    console.log(str.match(regNoG));
    // 输出:
    // ['100', index: 4, input: '价格是100元,进价是80,赚了20', groups: undefined]

    四、RegExp.prototype.execmatch 的区别

    4.1 exec 的逐次匹配特性

    exec 是正则对象的方法,无论是否带 g,它都返回一个匹配结果 (包括捕获组),并更新正则对象的 lastIndex 属性。通过循环可以遍历所有匹配。

    javascript 复制代码
    const 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,位置 15

    4.2 核心区别对照表

    特性 match (带/g) exec (循环调用)
    返回值 所有匹配的子串数组 一个匹配详情对象(含捕获组)
    捕获组 丢弃,只返回全匹配 保留 ,可通过 [1][2] 访问
    索引信息 包含 indexinput
    使用场景 简单提取所有匹配项 需要捕获组或位置信息时

    经典案例:驼峰转换需要捕获连字符后的字母

    复制代码
     

    五、String.prototype.replace ------ 强大的替换艺术

    5.1 基础替换与分组引用

    replace 可以用正则配合 $1$2 等引用捕获组。

    javascript 复制代码
    let 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 回调函数参数详解

    javascript 复制代码
    str.replace(regex, (match, p1, p2, ..., offset, string) => {
      // match:    本次匹配到的完整子串
      // p1, p2... 捕获组匹配的内容
      // offset:   匹配子串在原字符串中的索引
      // string:   原字符串本身
      return 替换后的内容;
    });

    经典驼峰转换

    javascript 复制代码
    const 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 中对应的值。

    javascript 复制代码
    let template = '我是{{name}},年龄{{age}},性别{{sex}}';
    let person = { name: 'lgl', age: 17, sex: '男' };
    console.log(render(template, person));
    // 输出:'我是lgl,年龄17,性别男'

    6.2 方式一:replace 一次性替换(推荐)

    javascript 复制代码
    function render(template, data) {
      return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
        return data[key] || '';
      });
    }

    6.3 方式二:递归 + exec(源自笔试题笔记)

    使用 exec 手动匹配并逐步替换,每次只处理第一个 {``{}},递归直到没有模板标记。

    javascript 复制代码
    function 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 是最简实现

    正则和字符串方法是前端工程师的"瑞士军刀",掌握它们能让代码更加简洁、健壮。希望本文能帮你彻底理清这些易混淆的概念,并在实际项目中灵活运用。

    最后留一个小思考:如果模板中出现了嵌套 {``{}} 或数组循环,该如何扩展这个模板引擎?欢迎在评论区交流讨论。


    如果觉得有帮助,请点赞 👍 收藏 ⭐ 关注,更多硬核前端文章持续输出中!

相关推荐
智码看视界2 小时前
Web Storage 的无障碍实践与工程化应用
前端·javascript·web
周末也要写八哥2 小时前
线程的生命周期之线程睡眠
java·开发语言·jvm
右耳朵猫AI2 小时前
Python周刊2026W22 | Django 6.1 Alpha 1发布、Nuitka 4.1发布、PEP 831终稿、PEP 808已接受
开发语言·python·django
半个烧饼不加肉2 小时前
JS 底层探究-- 普通函数和构造函数
开发语言·javascript·原型模式
小白不白1112 小时前
C# WinForm 与 VP 二次开发
开发语言·c#
程序猿乐锅2 小时前
【JAVASE | 第十七篇】Java 网络通信
java·开发语言
飞舞哲2 小时前
三维点云最小二乘拟合MATLAB程序
开发语言·算法·matlab
有点。2 小时前
C++(贪心算法二)
开发语言·c++·贪心算法
meilindehuzi_a2 小时前
透视 V8 底部:从物理内存到函数式哲学,重新解构 JavaScript 数组
开发语言·javascript·ecmascript