欢迎光临
我们一直在努力

拼多多面试题:手写手机号验证,带你把 JS 正则表达式从入门到实战

写在前面的

前端面试中,正则表达式是一道绕不过去的坎。本文以一道拼多多笔试题为引子——“如何验证用户输入的手机号是否正确?”,把 JS 中正则的核心知识点串起来,从最基础的字符匹配讲到模板引擎的简单实现。

永远不要相信用户的输入——把用户当小白,做好防御性校验。


一、面试题拆解:验证手机号

题目要求很明确,手机号校验规则如下:

  • 必须是 11 位数字
  • 必须以 1 开头
  • 第二位不能是 0、1、2(即只能是 3~9)
  • 剩下是 9 位数字
  • 我们先把正则写出来,再一步步解释背后的语法:

    let reg = /^1[3-9]\\d{9}$/;
    console.log(reg.test('13800000090')); // true

    看起来只有短短一行,但它包含了正则表达式中最核心的几个概念。下面我们逐个拆解。


    二、正则表达式是什么?

    你可以把正则表达式理解成一种"模式识别"的语言——就像你给搜索引擎描述一个东西长什么样,它帮你在一堆文字里把符合描述的统统找出来。

    打个比方:正则就像一个筛子,你定义好筛孔的形状(模式),把文本往里一倒,符合条件的留下,不符合的漏掉。

    6ff33d43706904762b8013ed1fccb5d3.jpg

    JS 中正则的写法是夹在两个 / 之间:

    /模式/

    每次默认匹配一个字符,想要匹配更多?配合后面讲到的量词就行。


    三、核心语法拆解

    3.1 [] —— 字符类:划定范围

    [] 表示匹配方括号内任意一个字符。注意,是一个,不是多个。

    /[abc]/ // 匹配 a、b、c 中的任意一个字符
    /[0-9]/ // 匹配 09 之间的任意一个数字(- 在 [] 内表示范围)
    /[3-9]/ // 匹配 39 之间的任意一个数字

    比喻:[] 就像一个单选题的选项集合——A、B、C、D 里你只能选一个。

    3.2 \\d —— 预定义字符类:数字快捷方式

    \\d 是 [0-9] 的简写,匹配任意一个数字。

    /\\d/ // 等价于 /[0-9]/

    同理还有:

    • \\w:匹配字母、数字、下划线([a-zA-Z0-9_])
    • \\s:匹配空白字符(空格、制表符、换行等)
    • \\D、\\W、\\S:上面三个的反义(大写 = 取反)

    3.3 {} —— 量词:重复次数

    {n} 表示前面的字符恰好出现 n 次。

    /\\d{9}/ // 匹配 9 个连续数字
    /[a-z]{3}/ // 匹配 3 个连续小写字母

    其他常见量词:

    量词含义
    {n} 恰好 n 次
    {n,} 至少 n 次
    {n,m} n 到 m 次
    + 至少 1 次,等价于 {1,}
    * 0 次或多次,等价于 {0,}
    ? 0 次或 1 次,等价于 {0,1}

    3.4 ^ 和 $ —— 锚点:定死边界

    这是最容易忽略但面试常考的点。

    • ^:匹配字符串的开头
    • $:匹配字符串的结尾

    /1[3-9]\\d{9}/ // 匹配 "abc13800000090xyz" 也行,只要**包含**就行
    /^1[3-9]\\d{9}$/ // 必须**整个字符串**完全匹配,不能多也不能少

    比喻:不加 ^$ 相当于"菜里有肉就行";加了 ^$ 相当于"整盘菜只能是肉,不能有其他东西"。


    四、回到手机号正则

    现在再看这行正则,应该一眼就看懂了:

    /^1[3-9]\\d{9}$/

    片段含义
    ^ 从字符串开头开始
    1 第一位必须是 1
    [3-9] 第二位是 3~9 之间的任意数字
    \\d{9} 后面跟 9 位数字
    $ 到字符串结尾结束

    拼起来:以 1 开头,第二位 3~9,后面 9 位数字,总共 11 位,完美。

    补充:typeof 的小彩蛋

    你可能不知道,正则表达式在 JS 中是什么类型?

    console.log(typeof /abc/); // "object"
    console.log(Object.prototype.toString.call(/abc/)); // "[object RegExp]"

    正则本质上是一个对象,类型是 RegExp。所以它才有 .test()、.exec() 这些方法。


    五、实战一:提取字符串中的数字

    场景:从一段描述文本中提取所有金额。

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

    这里引入了两个新概念:

    5.1 + 量词:匹配一次或多次

    \\d+ 表示匹配至少一个连续数字。对比一下:

    'abc123def'.match(/\\d/); // ["1"] —— 只匹配一个数字
    'abc123def'.match(/\\d+/); // ["123"] —— 尽可能多地匹配连续数字

    + 是贪婪的——在满足条件的前提下,能吞多少吞多少,像一个贪吃蛇。

    5.2 g 修饰符:全局匹配

    不加 g,match 匹配到第一个就收工;加了 g,它会把整个字符串扫完,把所有匹配结果放进数组返回。

    '价格100,进价80'.match(/\\d+/); // ["100"] —— 第一次就停了
    '价格100,进价80'.match(/\\d+/g); // ["100", "80"] —— 全部找出来

    比喻:不加 g 是"找到第一个就下班";加了 g 是"地毯式搜索,一个不漏"。


    六、实战二:replace + 分组 —— 烤串转驼峰

    场景:CSS 属性名 background-color 转成 JS 的驼峰命名 backgroundColor。

    先看一个常见的写法,再慢慢拆:

    const res = "hello-world".replace(/-(\\w)/, (_, c) => {
    return c.toUpperCase();
    });
    console.log(res); // "helloWorld"

    这里涉及两个核心机制:捕获分组和replace 的回调。

    6.1 () —— 捕获分组

    正则中的 () 有两个作用:

  • 分组:把多个字符当作一个整体
  • 捕获:把括号内匹配到的内容"抓取"出来,后续使用
  • const reg = /-(\\w)/;
    const result = "hello-world".match(reg);
    console.log(result);
    // ["-w", "w"]
    // ↑ ↑
    // 整体 第一个括号捕获的

    match 在不加 g 时返回一个数组,第一项是完整匹配,后面依次是每个括号捕获到的内容。

    6.2 replace 的回调函数

    str.replace(reg, callback) —— 每次匹配成功,回调函数就被调用一次:

    • 第一个参数:完整匹配的字符串(这里是 -w)
    • 后续参数:依次是每个捕获分组的内容(这里是 w)

    "hello-world".replace(/-(\\w)/, (fullMatch, captured) => {
    console.log(fullMatch); // "-w"
    console.log(captured); // "w"
    return captured.toUpperCase(); // "W"
    });
    // 结果:"helloWorld"

    整个流程:找到 -w → 捕获到 w → 把 w 转大写 → 用 W 替换 -w。

    6.3 一步到位的写法

    利用 $1 这个内置变量(代表第一个捕获分组),可以更简洁:

    "hello-world".replace(/-(\\w)/g, '$1'.toUpperCase());


    七、实战三:手写一个迷你模板引擎

    在这里插入图片描述

    面试高阶题:给你一个模板字符串和一组数据,实现变量替换。

    let template = `我是{{name}},年龄{{age}},性别{{sex}}`;
    let person = {
    name: "张三",
    age: 18,
    sex: "男"
    };

    期望输出:"我是张三,年龄18,性别男"

    用正则可以优雅地解决:

    function render(template, data) {
    const reg = /\\{\\{(\\w+)\\}\\}/g;
    // 只要模板里还能找到 {{xxx}},就一直替换
    while (reg.test(template)) {
    const key = reg.exec(template)[1]; // 提取变量名
    template = template.replace(reg, data[key]);
    }
    return template;
    }

    console.log(render(template, person));
    // "我是张三,年龄18,性别男"

    逐行拆解

    第一步:正则 /\\{\\{(\\w+)\\}\\}/g

    • \\{\\{:匹配两个花括号(花括号在正则中是特殊字符,需要 \\ 转义)
    • (\\w+):捕获分组,匹配一个或多个字母/数字/下划线,即变量名
    • \\}\\}:匹配两个闭合花括号
    • g:全局模式,因为模板里可能有多个 {{xxx}}

    第二步:reg.test(template) —— 检查还有没有待替换的占位符

    第三步:reg.exec(template)[1] —— 获取第一个捕获分组的内容,即变量名(如 name)

    注意:带 g 修饰符的正则在 exec 时会记住上次匹配的位置(通过 lastIndex),所以每次 exec 会匹配下一个占位符。这里用 test 配合 exec 逐个处理。

    第四步:template.replace(reg, data[key]) —— 替换当前占位符为实际值

    第五步:递归,直到没有占位符为止。

    递归版本的问题和改进

    上面的递归版本有个小问题:每次 replace 后用同一个 reg 可能因为 lastIndex 状态导致 bug。更稳健的写法:

    function render(template, data) {
    return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {
    return data[key] !== undefined ? data[key] : match;
    });
    }

    一次性匹配所有占位符,通过回调函数逐个替换。简洁、安全、高效。

    比喻:模板引擎就像填空题——模板是题目(我是___),数据是答案(name: "张三"),正则负责找到所有空位,并填上正确答案。


    八、核心方法速查表

    方法用途返回值
    reg.test(str) 检查是否匹配 true / false
    str.match(reg) 提取匹配内容 数组(无 g 也包含分组信息)或 null
    str.replace(reg, newStr/cb) 替换匹配内容 新字符串
    reg.exec(str) 逐次匹配(配合 g) 数组(含分组信息)或 null

    九、修饰符一览

    修饰符含义
    g global,全局匹配,找到所有
    i ignoreCase,忽略大小写
    m multiline,多行模式,^$ 匹配每行的开头结尾

    十、总结

    本文从一道拼多多面试题出发,串联起了 JS 正则的核心知识点:

  • [] 字符类:划定允许的字符范围
  • \\d、\\w 预定义类:常用字符的快捷写法
  • {}、+、* 量词:控制匹配次数
  • ^、$ 锚点:锁定字符串边界
  • () 捕获分组:提取感兴趣的部分
  • g 修饰符:全局匹配 vs 单次匹配
  • test / match / replace / exec:四大核心方法
  • 总结正则的要义:模式描述 + 量词控制 + 分组提取。掌握了这三板斧,日常开发中 80% 的正则场景都能从容应对。


    赞(0)
    未经允许不得转载:171主机测评 » 拼多多面试题:手写手机号验证,带你把 JS 正则表达式从入门到实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址