第10章 正则表达式

为什么需要正则表达式

  • 正则表达式可以用来:
    • 操作HTML节点中的字符串
    • 使用CSS选择器表达式定位部分选择器
    • 判断一个元素是否具有指定的类名(class)
    • 输入校验
    • 其他任务

正则表达式进阶

正则表达式说明

创建正则表达式

// 字面量创建
const pattern = /test/ig;
// 实例化正则对象
const pattern = new RegExp("test", "ig");

当正则表达式在开发环境是明确的,推荐有限使用字面量语法;当需要再运行时动态创建字符串来构建正则表达式时,则使用构造函数的方式

5个修饰符

修饰符 作用
i ignore,忽略大小写
g global,查找所有匹配项
m multiple,允许多行匹配,对获取textarea元素的值很有用
y 开启粘连匹配。正则表达式执行粘连匹配时试图从最后一个匹配的位置开始
u 允许使用Unicode点转义字符(\u{...})

术语和操作符

名称 示例 示例含义
精确匹配 /test/ 4个字母必须完全出现在所匹配的字符串中
匹配字符集 [abc] 匹配a、b、c中的任意一个字符
[^abc] 匹配除了a、b、c的任意字符
[a-z] 匹配从a到z所有小写字母
转义 \[\] 让程序将[]识别为字符,而不是操作符
起止符号 /^test/ 匹配以test开头的字符串
/test$/ 匹配以test结束的字符串
重复出现 /t?est/ 同时匹配est和test
在字符后面加?表示这个字符可以出现0次或1次
/t+est/ 匹配test、ttest、tttest等
在字符后面加+表示这个字符可以出现1次或多次
/t*est/ 匹配est、test、ttest、tttest等
在字符后面加*表示这个字符可以出现0次或1次或多次
/a{4}/ 匹配4个连续的字符a
用大括号指定重复次数
/a 匹配2~4个连续的字符a
/a 匹配2个或更多个连续的字符a
分组 /(ab)+ 匹配一个或多个连续的ab
括号扩起表示一个整体,共享后面的操作符
或(or) /a|b/ 匹配字符a或者字符b
反向引用 /^([dtn])a\1/ 匹配以字符d、t或n开头,其后连接字符a,
再后连接一个第一个分组(也就是括号中)中捕获的内容
\1匹配的具体字母要在运行时才能确定
引用正则中定义的捕获,第一个分组的捕获为\1,第二个为\2
反向引用在匹配XML类型的标记元素时很有用,如/<(\w+)>(.+)<\/\1>/

表示重复出现的运算符默认是贪婪模式,可以匹配所有可能的字符。在运算符后添加?使运算符为非贪婪模式,只进行最小限度的匹配。如对于字符串aaa,/a+/会匹配aaa,/a+?/则会匹配a、a、a。

预定义元字符 匹配的字符集
\t 水平制表符
\b 空格
\v 垂直制表符
\f 换页符
\r 回车符
\n 换行符
\cA:\cZ 控制字符
\u0000:\uFFFF 十六进制Unicode码
\x00:\xFF 十六进制ASCII码
. 匹配除换行字符(\n、\r、\u2028和\u2029)之外的任意字符
\d 匹配任意十进制数字,等价于[0-9]
\D 匹配除了十进制数字的任意字符,等价于[^0-9]
\w 匹配任何字母、数字和下划线,等价于[A-Za-z0-9_]
\W 匹配除了字母、数字和下划线的任意字符,等价于[^A-Za-z0-9_]
\s 匹配任意空白字符(包括空格、制表符、换页符等)
\S 匹配除了空白字符的任意字符
\b 匹配单词边界
\B 匹配非单词边界(单词内部)

编译正则表达式

创建正则表达式的两种方法

// 字面量创建
const re1 = /test/i;
// 实例化正则对象
const re2 = new RegExp("test", "i");

console.log(re1.toString() === "/test/i");
// true

// 用test方法检验字符串
console.log(re1.test("Test"));
// true
console.log(re2.test("tesi"));
// false

// 每个正则对象都是独一无二的
console.log(re1.toString() === re2.toString());
// true
console.log(re1 === re2);
// false

在运行时编译一个供稍后使用的正则表达式

<div id="wango" class="human student son brother"></div>
<span></span>
<div id="lily" class="human student daughter sister"></div>
<div id="jack" class="human teacher son father"></div>
<script>
    function findClassInElements(className, type) {
        // getElementsByTagName方法返回的不是数组,是 HTMLCollection(HTML集合)
        // 在WebKit内核的浏览器中返回一个 NodeList,所以不能使用数组方法
        const elems = document.getElementsByTagName(type || "*");

        // 类名的规则:直接开头或者以空格开头,直接结束或者以空格结束
        // 当创建正则表达式字面量时,只需使用一个反斜线(\s)
        // 但这里是在字符串中写反斜线,就必须使用双反斜线(\\s)进行转义
        const reg = new RegExp("(^|\\s)" + className + "(\\s|$)");
        const results = [];
        // 在for循环中先将长度储存,避免每次循环都去访问对象再返回长度,
        // 以优化性能(长度不能是动态的)
        for (let i = 0, len = elems.length; i < len; i++) {
            if (reg.test(elems[i].className)) {
                results.push(elems[i]);
            }
        }
        return results;
    }

    findClassInElements("student", "div").forEach(elem => {
        console.log(elem.id);
    });
    // wango
    // lily

    findClassInElements("son").forEach(elem => {
        console.log(elem.id);
    });
    // wango
    // jack
</script>

捕获匹配的片段

执行简单捕获

<div id="square" style="transform: translateY(15px);background-color: black;"></div>
<script>
    function getTransformY(elem) {
        const transformValue = elem.style.transform;
        if (transformValue) {
            // 用括号分组,使用match方法(由字符串调用)捕获匹配的值,返回数组
            const match = transformValue.match(/^translateY\(([^\)]+)\)$/);
            // const match = transformValue.match(/^translateY\((.+)\)$/);
            // match方法匹配结果通过第一个索引返回,然后每次捕获结果索引递增
            return match ? match[1] : "";
        }
        return "";
    }

    const square = document.getElementById("square");
    console.log(getTransformY(square));
    // 15px
</script>

使用全局表达式进行匹配

const html = '<div class="test"><b>hello</b><i>world!</i></div>';

// 使用局部正则表达式可以返回数组,
// 数组包含第一个匹配的全部内容以及操作中的全部捕获结果
const results = html.match(/<(\/?)(\w+)([^>]*?)>/);
console.log(results[0]);    // <div class="test">
console.log(results[1]);    // 
console.log(results[2]);    // div
console.log(results[3]);    // class="test"

// 使用全局正则表达式可以返回数组
// 数组包含全部匹配结果,但不返回捕获内容
const all = html.match(/<(\/?)(\w+)([^>]*?)>/g);
console.log(all[0]);        // <div class="test">
console.log(all[1]);        // <b>
console.log(all[2]);        // </b>
console.log(all[3]);        // <i>
console.log(all[4]);        // </i>
console.log(all[5]);        // </div>

let match;
const rslts = [];
const reg = /<(\/?)(\w+)([^>]*?)>/g;
// 使用exec方法(正则调用)进行捕获和全局搜索
// 每次调用都可以返回下一个匹配的结果,
// 结果包含第一个匹配的全部内容以及操作中的全部捕获
while ((match = reg.exec(html)) !== null) {
    rslts.push(match);
}

console.log(rslts[0][0]);   // <div class="test">
console.log(rslts[0][1]);    // 
console.log(rslts[0][2]);    // div
console.log(rslts[0][3]);    // class="test"

捕获的引用

<b class="hello">Hello</b><i>world!</i>
<script>
    const html = '<b class="hello">Hello</b><i>world!</i>';
    // 使用\1指向正则表达式中的第1个捕获
    const reg = /<(\w+)([^>]*)>(.*?)<\/\1>/g    // 仅作示例,并不完美
    let match = reg.exec(html);

    console.log(match[0]);  // <b class="hello">Hello</b>
    console.log(match[1]);  // b
    console.log(match[2]);  //  class="hello"
    console.log(match[3]);  // Hello
</script>

未捕获的分组

// 用作分组的括号也会被识别为捕获内容
const reg1 = /((hello-)+)world/;
let match = "hello-hello-world".match(reg1);
console.log(match[0]);  // hello-hello-world
console.log(match[1]);  // hello-hello-
console.log(match[2]);  // hello-

// 在起始圆括号之后使用?:创建被动子表达式,使不被捕获
const reg2 = /((?:hello-)+)world/;
match = "hello-hello-world".match(reg2);
console.log(match[0]);  // hello-hello-world
console.log(match[1]);  // hello-hello-
console.log(match[2]);  // undefined

利用函数进行替换(replace方法)

// 基本用法
console.log("ABCDEfg".replace(/[A-Z]/g, "X"));
// XXXXXfg

// replace方法第二个参数可以是函数,
// 这时,对每一个匹配到的值都会调用一次这个函数,
// 从函数返回的值作为匹配字符串的替换值

// 将下划线连接的字符串转换为小驼峰字符串
// 第二个参数函数接收多个参数,第一个是匹配的字符串,之后的参数是捕获结果
console.log("this_is_an_array".replace(/_(\w)/g, (all, letter) => letter.toUpperCase()));
// thisIsAnArray

// 一种查询字符串压缩技术
function compress(str) {
    const keys = {};    // 存储键值对
    // 提取键值对信息
    str.replace(/([^=&]+)=([^&]*)/g, (all, key, value) => {
        keys[key] = (keys[key] ? keys[key] + "," : "") + value;
        return "";  
        // 只是利用replace的查找和调用函数功能,
        // 不在意执行结果,所以可以返回任意值
    });
    // 收集key信息
    const result = [];
    for (let key in keys) {
        result.push(key + "=" + keys[key]);
    }
    return result.join("&");
}

const str = "foo=1&foo=2&blah=a&blah=b&foo=3";
console.log(compress(str));
// foo=1,2,3&blah=a,b

使用正则表达式解决常见的问题

匹配换行

const html = "<b>Hello</b>\n<i>world!</i>";
console.log(/.*/.exec(html)[0]);    // 不匹配换行符
// <b>Hello</b>
console.log(/[\S\s]*/.exec(html)[0]);   // 匹配全部字符(最优解)
// <b>Hello</b>
// <i>world!</i>
console.log(/(?:.|\s)*/.exec(html)[0]); // 匹配换行符
// <b>Hello</b>
// <i>world!</i>

匹配Unicode字符

const text = "\u6211\u7231\u4f60\uff0c\u4e2d\u56fd\uff01";
const mathchAll = /[\w\u0080-\uFFFF_-]+/; // 匹配所有字符,包括Unicode
console.log(text.match(mathchAll)[0]);
// 我爱你,中国!

匹配转义字符

// 允许任意字符序列组成的词,包括一个反斜线紧跟
// 任意字符(包括反斜线本身),或兼而有之
const pattern = /^((\w+)|(\\.))+$/;

// 测试字符串,除了最后一个,其他均通过
const tests = [
    "formUpdate",
    "form\\.update\\.whatever",
    "form\\:update",
    "\\f\\o\\r\\m\\u\\p\\d\\a\\t\\e",
    "form:update"
];

let flag = true;
for (let i = 0, len = tests.length; i < len; i++) {
    if (!pattern.test(tests[i])) {
        flag = false;
        console.log(tests[i], "is invalid!");
    };
}
console.log(flag);
// form:update is invalid!
// false
posted @ 2020-11-27 21:23  LiuWango  阅读(169)  评论(0编辑  收藏  举报