6

项目中遇到一些的字符串处理场景:

  1. 对整型金额进行千分位格式化,如:1,000
  2. 表单字段校验,如姓名只能是中文且长度范围是[2,4]
  3. 身份证号保留前6后4,中间8位掩码处理。

对于这些都可以使用正则表达式完成。如果你还不知道该怎么用,那有必要学习下正则表达式了。

一、RegExp函数

正则表达式专门用于描述字符串的字符组合格式。我们经常对字符串判断是否匹配指定的正则表达式,以及对匹配的字符进行操作(如替换,转换等)。

1.1 创建正则表达式对象

RegExp用来表示正则表达式。RegExp即是构造函数也是个工厂函数。
创建正则表达式的方式:

// 字面量方式
/pattern/flags

// 构造函数方式
new RegExp(pattern [, flags])

// 工厂函数方式
RegExp(pattern [, flags])

1.2 exec(String)方法

对指定对字符串进行一次正则匹配操作。如果匹配成功则返回一个数组,否则返回null。
返回的数组:

  1. 第一个元素是匹配成功对字符串;
  2. 从第二个元素开始都是匹配的捕获分组字符串;
  3. 该数组被添加了两个额外属性:
    3.1 input

     表示输入原始字符串,即exec方法的实参。

    3.2 index

     表示匹配的字符串在输入参数的下标位置。
    
// matchs1为["ab", "b", index: 0, input: "abcabc"]
var matchs1 = /a(b)/.exec('abcabc'); // 捕获分组

// matchs2为["ab", index: 0, input: "abcabc"]
var matchs2 = /ab/.exec('abcabc');

1.3 test(String)方法

判断指定的字符串是否匹配正则表达式。

1.4 flags(i,g,m)

控制匹配行为。

1.5 lastIndex属性

表示正则表达式下次执行匹配操作时字符串下标位置。

var r = /a(b)/g;
console.log(r.lastIndex); // 0
r.test('abcabc'); //true, 从字符串下标0开始匹配
console.log(r.lastIndex); // 2
r.test('abcabc'); //true, 从字符串下标2开始匹配
console.log(r.lastIndex); // 5

注意几点:

  1. 只有指定全局匹配时,lastIndex才有效,否则一直为0;

    var r = /a(b)/; // 没有指定全局m
    console.log(r.lastIndex); // 0
    r.test('abcabc'); //true, 从字符串下标0开始匹配
    console.log(r.lastIndex); // 0
    r.test('abcabc'); //true, 从字符串下标0开始匹配
    console.log(r.lastIndex); // 0
  2. 每次执行全局模式匹配(通过exec或者test方法)时都会影响lastIndex的值。
    注意下例中多次调用会出现匹配失败的情况:

    var r = /a(b)/g;
    console.log(r.lastIndex); // 0
    r.test('abcabc'); //true, 从字符串下标0开始匹配
    console.log(r.lastIndex); // 2
    r.test('abcabc'); //true, 从字符串下标2开始匹配
    console.log(r.lastIndex); // 5
    r.test('abcabc'); //false, 从字符串下标5开始匹配
    console.log(r.lastIndex); // 0,下次又从头开始匹配了
  3. lastIndex属性是可读写的。

    var r = /a(b)/g;
    console.log(r.lastIndex); // 0
    r.test('abcabc'); //true, 从字符串下标0开始匹配
    console.log(r.lastIndex); // 2
    r.test('abcabc'); //true, 从字符串下标2开始匹配
    console.log(r.lastIndex); // 5
    r.lastIndex = 0; // 修改为0,下次从头开始
    r.test('abcabc'); //true, 从字符串下标0开始匹配
    console.log(r.lastIndex); // 0,下次又从头开始匹配了

二、String中相关的方法

Sting对象的一些方法都接受正则对象的参数:

  1. replace
  2. split
  3. match
  4. search

三、 正则表达式语法

详细的见参考,这里罗列了一些笔记,首先是一张正则知识简图:

clipboard.png

  1. 元字符作为一般字符匹配时需要转移;
  2. 除了可以匹配字符外,还可以匹配位置,即图中【位置匹配】部分;
  3. 量词
  • 默认贪婪模式,用?修饰量词转成非贪婪模式;
  • 可以修饰"字符匹配","捕获分组","非捕获分组";
  • 不能修饰"位置匹配(包含后瞻)"。

量词修饰零宽度的位置匹配是没有意义的

  1. 【候选】的优先级最低;

3.1 关于【位置匹配】

正则除了可以匹配字符外还可以匹配位置,图中箭头指向的地方:

clipboard.png
图片引用《JavaScript正则迷你书》
位置也是有特性的,并且一个位置可以有多个特性,如字符串abc123, 正则表达式/^/,/\b/, /(?=a)/都匹配最左侧的位置。除了常见的位置匹配字符^,$,\b,\B外,前瞻和后瞻也是位置匹配:

模式名称描述
(?=exp)正向前瞻匹配exp前面的位置
(?!exp)负向前瞻匹配不满足exp前面的位置,即匹配后面不满足expd的位置
(?<=exp)正向后瞻匹配exp后面的位置(ES6支持)
(?<!exp)负向后瞻匹配不满足exp后面的位置,即匹配前面不是exp的位置(ES6支持)
'abc123'.replace(/(?=1)/g, '#'); // abc#123, 匹配字符1前面的位置
'abc123'.replace(/(?!1)/g, '#'); // #a#b#c1#2#3#, 匹配不是字符1前面的位置,有多个位置符合要求
'abc123'.replace(/(?<=1)/g, '#'); // abc1#23, 匹配字符1后面的位置
'abc123'.replace(/(?<!1)/g, '#'); // #a#b#c#12#3#, 匹配不是字符1后面的位置

3.2 位置匹配总结

  1. 都是0宽度,不影响lastIndex;
  2. 可以对同一个位置同时进行多个匹配模式,相当于该位置要满足所有的匹配模式。

     /^^hello\b(?!\w)/.test("hello "); // true 

    位置匹配^重复匹配一个位置,相当于一个位置匹配;
    位置匹配\b(?!\w)都是对同一个位置进行匹配,字符b后面的位置即是个单词结尾又是非单词字符(本例是空格字符)的前面

  3. 密码规则校验问题
    密码字符集一般是有安全要求的,比如:必须包含数字,大小写字母等。可以使用【位置匹配】:

    // 规则1 = 长度6到15位,必须是数字,字母,下划线,感叹号,中划线
    /^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // true,很容易实现
    
    // 规则2 = 规则1 + 必须包含大写字母
    /(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // false
    /(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // true
    
    // 规则3 = 规则2 + 必须包含下划线
    /(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // false
    /(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123_'); // true

    原理就是通过找满足条件的位置来判断目标字符串是否符合指定的规则。规则2(规则3同理)的正则表达式相当于对起始位置添加了多个匹配条件。

3.2 回溯

为了判断是否匹配完成,进行了一些尝试。尝试的副作用就是造成回溯(造成匹配位置倒退的行为)。
回溯会影响性能,写正则尽量规避造成回溯。

四、 项目中的实战

1. 金额格式化,形如:1,000:

  • 遇到的写法1
function reverse(str) {
     return str.split('').reverse().join('');
}
function format(num) {
    return reverse(reverse(num + '').replace(/(\d{3})(?!$)/g, '$1,'));
}
format(123); // '123'
format(1234); // '1,234'
format(12345); // '12,345'
format(123456); // '123,456'
  • 遇到的写法2
function format(num) {
    return (num + '').replace(/(?!^)(?=(\d{3})+$)/g, ',')
}

一个正则搞定!不过也不可否认,复杂的正则让人难以理解!!!

2. 表单字段校验,如只能是字母和数字,且长度范围[6,18]

// 正则表达式使用了i flag
function validate(str) {
    return /^[a-z0-9]{6,18}$/i.test(str);
}

3. 表单字段校验,如不准许字符重复三次以上

function validate(str) {
    return return /(.)\1{2,}/i.test(str);
}

正则表达式使用了捕获分组和反向引用

4. 身份证号保留前6后4,中间8位掩码处理

function mask(idNo) {
    return idNo.replace(/^(\d{6})\d{8}(.+)$/, '$1********$2')
}

5. 判断一个字符串的所有字符是否都相同

function validate(str) {
    return /^(.)\1*$/.test(str);
}

6. 银行卡号格式化:每4位添加个空格

function formatId(idNo) {
    return idNo.replace(/(\d{4})(?!$)/g, '$1 '); // 正则类似例1中的*金额格式化*
}

7. 找出重复的字符串

这个是之前看到的问答JS找出重复的字符串,这里用正则实现下:

function getSameStr(str, n) {
    var regExp = new RegExp(`(?=(.{${n}}))(?=.{${n},}1)`, 'g');
    var matchedStr = []
    var match = null;
    
    while(match = regExp.exec(str)) {
        matchedStr.push(match[1]);
        regExp.lastIndex++; // 正则是0宽度的,需要手动处理lastIndex。
    }
    
    return matchedStr;
}

// Test
var result = getSameStr('712359123504', 2)
console.log(result) // ["12", "23", "35"]

详细的实现过程和思路戳这里

好玩的正则应用场景持续补充ing

参考

整理自gitHub笔记:RegExp


普拉斯强
2.7k 声望53 粉丝

Coder