# 位置匹配对应的练习题

# 验证密码问题

  • 规则一:密码长度 6-12 位,由数字、小写字母和大写字母组成
  • 规则二:至少包括 2 种字符。

# step 1

先写出满足规则一的正则表达式:

横向匹配:长度使用量词 {m,n} 即可; 纵向匹配:数字、小写字母、大写字母;

const regexp = /[0-9a-zA-Z]{6,12}/;

# step 2 判断包含某一种字符串?

假设要求必须包含数字,该怎么使用正则进行判断呢?

我首先想到的是量词 +, 即至少出现一次

const regexp = /[0-9]+/;

或者

const regexp = /[0-9]{1,12}/;

但是这种方式不能和 step 1 中的正则表达式合并起来,只能按照顺序逐个检测是否符合规则。
(这里不考虑最笨的暴力枚举)

作者的思路是通过位置来进行判断,即

var regex = /(?=.*[0-9])^[0-9A-Za-z]{6,12}$/;

为什么这个正则表达式可以呢?我们来分析下:

这里使用了正向先行断言,子模式是.*[0-9], 其中

  • .* 表示任意字符出现任意次
    和 [0-9a-zA-Z]{0,11} 的作用相同
  • [0-9] 表示数字

合并起来字面意思就是:寻找位于任意字符 + 数字前面的位置。

翻译成符合人类思维就是:step 1 中最后匹配的结果,需要存在符合子模式.*[0-9] 的字符串

存在符合子模式.*[0-9] 的字符串可以是下面的组合:

  • 数字开头
    1234ab

    🍬

    var regex = /.*[0-9]/;
    var string = "1234ab";
    console.log(string.match(regex));
    // => ["1234"]
    
  • 非数字开头
    ab1234

    🍬

    var regex = /.*[0-9]/;
    var string = "ab1234";
    console.log(string.match(regex));
    // => ["ab1234"]
    
  • 数字结尾
    1234ab12
    ab12345

    🍬

    var regex = /.*[0-9]/;
    var string = "1234ab12";
    console.log(string.match(regex));
    // => ["1234ab12"]
    
  • 非数字结尾
    1234ab
    ab1234ee

    🍬

    var regex = /.*[0-9]/;
    var string = "ab1234ee";
    console.log(string.match(regex));
    // => ["ab1234"]
    

    从上面几个分类中可以看出,只要 step 1 中最后匹配的结果中存在符合子模式.*[0-9]的字符串,那就一定有数字。

    # step 3 同时包含具体两种字符

    step 2 的巧妙在于,通过位置匹配来校验 step 1 的结果。

    同时包含小写字母和数字

    var regex = /(?=.*[a-z])(?=.*[0-9])^[0-9A-Za-z]{6,12}$/;
    

    同时包含大写字母和数字

    var regex = /(?=.*[A-Z])(?=.*[0-9])^[0-9A-Za-z]{6,12}$/;
    

    同时包含小写字母和大写字母

    var regex = /(?=.*[a-z])(?=.*[A-Z])^[0-9A-Za-z]{6,12}$/;
    

    # 最终

    🍬

    var regex = /(((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[A-Z])))^[0-9A-Za-z]{6,12}$/;
    console.log( regex.test("1234567") );     // false 全是数字
    console.log( regex.test("abcdef") );      // false 全是小写字母
    console.log( regex.test("ABCDEFGH") );    // false 全是大写字母
    console.log( regex.test("ab23C") );       // false 不足6位
    console.log( regex.test("ABCDEF234") );   // true 大写字母和数字
    console.log( regex.test("abcdEF234") );   // true 三者都有
    

    # 负向先行断言

    至少包含两种字符,可以反向理解成:不能只有一种字符,就可以用到负向先行断言了。

    • 不能全是数字
    let regex = /(?!^[0-9]{6,12}$)^[0-9A-Za-z]{6,12}$/;
    
    • 不能全是小写字母
    let regex = /(?!^[a-z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
    
    • 不能全是大写字母
    let regex = /(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
    

    注意负向先行断言的子模式:^[A-Z]{6,12}$,里面的 ^ 和 $ 一定不能漏了

    因为期望 step 1 的结果不符合子模式

    var regex = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
    console.log( regex.test("ABCDEF234") ); // true 大写字母和数字
    

    如果没有 ^ 和 $,那么剩下的(?![A-Z]{6,12}) 实际上代表的是step 1 的结果中不能含有符合子模式的字符串。

    var regex = /(?![0-9]{6,12})(?![a-z]{6,12})(?![A-Z]{6,12})^[0-9A-Za-z]{6,12}$/;
    console.log( regex.test("ABCDEF234") ); // false 大写字母和数字