《JavaScript Ninja》之正则表达式

正则表达式 是一个拆分字符串并查询相关信息的过程。javascript

练习网站:JS Binhtml

正则表达式测试网站:Regular Expression Test Page for JavaScriptjava

正则表达式进修正则表达式

正则表达式 一般被称为 模式,是一个用简单方式描述或匹配一系列符合某个句法规则的字符串。express

建立正则表达式的两种方法:

(1)经过正则表达式字面量(推荐);编程

var pattern = /test/;

就像字符串是用引号进行界定同样,正则字面量是用 正斜杠 进行界定的。数组

(2)经过构造 RegExp 对象的实例。函数

var patter = new RegExp("test");

* 在开发中,若是正则式已知的,则优先选择字面量语法,而构造器方式则是用于在运行时,经过动态构建字符串来构建正则表达式。测试

正则表达式的3个标志——>这些标志将附加到字面量尾部:/test/ig,或者做为 RegExp 构造器的第二个字符参数:new RegExp("test","ig")优化

i:不区分大小写
g:匹配模式中的全部实例
m:容许匹配多个行

术语与操做符

精确匹配:若是一个字符不是特殊字符或操做符,则表示该字符必须在表达式中出现。

/test/:匹配字符串 test

匹配一类字符:匹配一个有限字符集中的某一个字符,能够把字符集放到中括号内,来指定该 字符集(类)操做符

若想匹配一组有限字符集之外的字符,能够经过在中括号第一个开括号的后面加一个插入符(^)来实现。

字符集的变异操做:制定一个范围。(-)

[abc]:匹配 "a","b","c" 中的任何一个字符
[^abc]:匹配除了 "a","b","c" 之外的任意字符
[a-m]:匹配 "a" 和 "m" 之间的任何一个小写字母

转义:使用反斜杠对任意字符进行转义,让被转义字符做为字符自己进行匹配。(在字符串中使用时,须要对反斜杠进行转义)

\[:匹配 [ 字符
\\:匹配 \ 字符

匹配开始或结束:插入符号(^)表示从字符串的开头进行匹配,美圆符号($)表示该模式必须出如今字符串的结尾

这里的插入符号只是 ^ 字符的一个重载,它还能够用于否认一个字符类集。

/^test$/:同时使用^和$,代表指定的模式必须包含整个候选字符串

重复出现:匹配任意数量的相同字符

  • ?:在一个字符后面加一个问号,定义为该字符是可选的(能够出现一次或根本不出现)

    /t?est/:能够匹配 "test" 和 "est"
  • +:若是一个字符要出现一次或屡次,可使用加号

    /t+est/:能够匹配 "test" "ttest" "tttest"
  • *:若是一个字符要出现0次或屡次,可使用星号

    /t*est/:能够匹配 "test" "ttest" "tttest" "est"
  • {n}:在字符后面的花括号里指定一个数字来表示重复次数

    /a{4}/:表示匹配含有连续四个 "a" 字符的字符串
  • {n,m}:在字符后面的花括号里指定两个数字(用逗号隔开),表示重复次数区间

    /a{4,10}/:表示匹配任何含有连续4个至10个 "a" 字符的字符串
  • {n,}:次数区间的第二个值是可选的(可是要保留逗号),表示一个开区间

    /a{4,}/:表示匹配任何含有连续4个或多于4个 "a" 字符的字符串

这些重复操做符能够是 贪婪的 非贪婪的。默认状况下,它们是 贪婪的:它们匹配全部的字符组合。

在操做符后面加一个问号 ? 字符(? 操做符的一个重载),如 a+?,可让该表达式编程称为 非贪婪的:进行最小限度的匹配。

// 对字符串 "aaa" 进行匹配
/a+/:将匹配全部这三个字符
/a+?/:只匹配一个 a 字符,由于一个 a 字符就能够知足 a+ 术语

预约义字符类:匹配一些不可能用字面量字符来表示的字符(如像回车这样的控制字符、小数位数或一组空白字符)

7-1

分组:若是将操做符应用于一组术语,能够像数学表达式同样在该组上使用小括号

/(ab)+/:匹配一个或多个连续出现的子字符串 "ab"
  • 当正则表达式有一部分是用括号进行分组时,它具备双重责任,同时也建立所谓的 捕获

或操做符(OR):能够用竖线 ( | ) 表示或者的关系。

/a|b/:匹配 "a" 或 "b" 字符
/(ab)+|(cd)+/:匹配出现一次或屡次的 "ab" 或 "cd"

反向引用:正则表达式中最复杂的术语是,在正则中所定义的 捕获 的反向引用,将 捕获 做为正则表达式中可以成功匹配术语时的候选字符串。

这种术语表示法是在 反斜杠 后面加一个要引用的捕获数量,该数字从1开始,如\一、\2等。

/^([dtn])a\1/:能够任意一个以 "d","t","n" 开头,且后面跟着一个 a 字符,而且再后面跟着的是和第一个捕获相同字符的字符串

不一样于 /[dtn]a[dtn]/,a 后面的字符有可能不是 "d" "t" 或 "n" 开头,但这个字符确定是以触发该匹配的其中一个字符 ("d" "t" 或 "n") 开头。所以,\1 匹配的字符须要在执行的时候才能肯定。

编译正则表达式

正则表达式的两个重要阶段:

(1)编译:发生在正则表达式第一次被建立的时候;

(2)执行:发生在咱们使用编译过的正则表达式进行字符串匹配的时候。

/*
 * 建立编译后正则表达式的两种方式
 * 正则表达式在建立以后都处于编译后的状态
 */
var re1 = /test/i;  // 经过字面量建立

var re2 = new RegExp("test", "i");  // 经过构造器建立

console.log(re1.toString() == "/test/i");   // true
console.log(re1.test("TesT"));  // true
console.log(re2.test("TesT"));  // true
console.log(re1.toString() == re2.toString());  // true
console.log(re1 != re2);    // true
  • 若是将 re1 的引用再替换成 /test/i 字面量,那么同一个正则表达式可能又被编译屡次,因此正则表达式只编译一次,并将其保存在一个变量中以供后续使用,这是一个重要的优化过程

注意:每一个正则表达式都有一个独立的对象表示,每次建立正则表达式(也所以被编译),都会为此建立一个新的正则表达式对象。和其余的原始类型不太同样,其结果将永远是独一无二的

特别重要的一点,用 构造器 建立正则表达式的使用,这种技术容许咱们,在运行时经过动态建立的字符串构建和编译一个正则表达式。对于构建大量重用的复杂表达式来讲,这是很是有用的。

[例子:]

<div calss="samurai ninja">a</div>
<div class="ninja samurai">b</div>
<div></div>
<span class="samurai ninja ronin">c</span>

<script>
    function findClassInElements(className, type) {

        var elems = document.getElementsByTagName(type || "*");

        /* 
         * 正则表达式匹配的字符串:
         * 要以空字符串或空格开始,
         * 然后跟着指定样式名称,
         * 而且紧随其后的是一个空白字符或结束字符串
         */
        var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");

        var results = [];

        for (var i = 0, length = elems.length; i < length; i++) {
            if (regex.test(elems[i].className)) {
                results.push(elems[i]);
            }
        }

        return results;
    }

    var results1 = findClassInElements("ninja", "div"); // <div class="ninja samurai">b</div>
    var results2 = findClassInElements("ninja", "span"); // <span class="samurai ninja ronin">c</span>
    var results3 = findClassInElements("ninja");    // <div class="ninja samurai">b</div>
                                                    // <span class="samurai ninja ronin">c</span>

    console.log(results1.length);   // 1
    console.log(results2.length);   // 1
    console.log(results3.length);   // 2
</script>
  • 在该例子的正则中,要注意 反斜杠(\\)的使用:\\s。建立带有反斜杠字面量正则表达式时,只须要提供一个反斜杠便可。可是,因为咱们在字符串中写反斜杠,因此须要 双反斜杠 进行转义。要明白,咱们是用字符串(而不是字面量)来构建正则表达式。

用正则表达式进行捕捉操做

正则表达式的 实用性 表如今捕获已匹配的结果上,这样咱们即可以在其中进行处理。

1. 执行简单的捕获 —— match()
  • match() 返回的数组的第一个索引的值老是该匹配的完整结果,而后是每一个后续捕获结果。

  • 记住,捕获是由正则表达式中的小括号所定义。

2. 用全局表达式(/g)进行匹配 —— match() exec()
  • 使用 局部正则表达式 时,返回的该数组包含了在匹配操做中成功匹配的整个字符串以及其余捕获结果。

  • 使用 全局正则表达式 时,匹配全部可能的匹配结果,返回的数组包含了全局匹配结果,而不只仅是第一个匹配结果。

使用 match()exec(),咱们老是能够找到想要寻找的精确匹配。

[例子1:]

// 使用 match() 进行全局搜索和局部搜索时的不一样
var html = "<div class='test'><b>Hello</b> <i>world!</i></div>";

// 局部正则表达式会返回一个数组,该数组包含了在匹配操做中匹配的整个字符串以及其余捕获结果
var results = html.match(/<(\/?)(\w+)([^>]*?)>/);   // 局部正则匹配

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

// 全局正则表达式返回一个数组,匹配全部可能的匹配结果,而不只仅是第一个匹配结果
var all = html.match(/<(\/?)(\w+)([^>]*?)>/g);  // 全局正则匹配

console.log(all);   // ["<div class='test'>", "<b>", "</b>", "<i>", "</i>", "</div>"]
console.log(all[0] == "<div class='test'>");    // true
console.log(all[1] == "<b>");   // true
console.log(all[2] == "</b>");  // true
console.log(all[3] == "<i>");   // true
console.log(all[4] == "</i>");  // true
console.log(all[5] == "</div>");    // true

[例子2:]

// 使用 exec() 方法进行捕获和全局搜索
var html = "<div class='test'><b>Hello</b> <i>world!</i></div>";

var tag = /<(\/?)(\w+)([^>]*?)/g, match;
var num = 0;

while ((match = tag.exec(html)) !== null) {
    console.log(match.length);  // 4
    num++;
}

console.log(num);   // 6
3. 捕获的引用

两种方法 ,能够引用捕获到的匹配结果:

(1)自身匹配

(2)替换字符串

与反向引用不一样,经过调用 replace() 方法替换字符串得到捕捉的引用时,使用 $1$2$3 语法表示每一个捕获的数字

/*
 * 使用反向引用匹配 HTML 标签内容
 */
var html = "<div class='hello'>Hello</b> <i>world!</i>";

var pattern = /<(\w+)([^>]*)>(.*?)<\/\1>/g; // 使用捕获的反向引用

var match = pattern.exec(html); // 在测试字符串上进行匹配

console.log(match); // ["<i>world!</i>", "i", "", "world!"]

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

console.log(match[0] == "<i>world!</i>");   // true
console.log(match[1] == "i");   // true
console.log(match[2] == "");    // true
console.log(match[3] == "world!");  // true

/*
 * replace() 方法得到捕捉的引用:使用 $一、$二、$3 语法表示每一个捕获的数字
 */
"fontFamily".replace(/([A-Z])/g, "-$1").toLowerCase();  // font-family
4. 没有捕获的分组

小括号的双重责任:(1)进行分组;(2)指定捕获。

  • 因为捕获和表达式分组都使用了小括号,因此没法告诉正则表达式处理引擎,哪一个小括号是用于分组,以及哪一个是用于捕获的。

  • 因此会将小括号既视为分组,又视为捕获,因为有时候须要指定一些正则表达式分组,因此会致使捕获的信息比咱们预期的还要多。

要让一组括号不进行结果捕获,正则表达式的语法容许咱们在开始括号后加一个 ?: 标记,这就是所谓的 被动子表达式

var pattern = /((?:ninja-)+)sword/;
// 外层小括号 ——> 定义捕获(sword以前的字符串)
// 内层小括号 ——> 针对 + 操做符,对 "ninja-" 文本进行分组
// ?: ——> 只会为外层的括号建立捕获,内层括号被转换为一个被动子表达式

利用函数进行替换

String 对象的 replace 方法:

(1)将正则表达式做为第一个参数,致使在该模式的匹配元素上进行替换。

"ABCDEfg".replace(/[A-Z]/g, "X");   // XXXXXfg,替换全部的大写字母为“X”

(2)(最强大特性)接受一个函数做为替换值,函数的返回值是即将要替换的值。(这样能够在运行时肯定该替换的字符串,并掌握大量与匹配有关的信息。)

/* 
 * 第一个参数:传入完整的字符串
 * 第二个参数:捕获结果
 */
function upper(all, letter) {
    return letter.toUpperCase();
}

/* 
 * 函数第一次调用时,传入 "-b" 和 "b"
 * 函数第二次调用时,传入 "-w" 和 "w"
 */
"border-bottom-width".replace(/-(\w)/g, upper); // borderBottomWidth

当替换值(第二个参数)是一个函数时,每一个匹配都会调用该函数并带有一串参数列表。

  • 匹配的完整文本。
  • 匹配的捕获,一个捕获对应一个参数。
  • 匹配字符在源字符串中的索引。
  • 源字符串。

[例子:]

/*
 * 让查询字符串转换成另一个符合咱们需求的格式
 * 原始字符串:foo=1&foo=2&blah=a&blah=b&foo=3
 * 转换成:foo=1,2,3&blah=a,b
 */
function compress(source) {
    var keys = {};  // 保存局部键

    source.replace(
        /([^=&]+)=([^&]*)/g,
        function(full, key, value) {
            keys[key] = (keys[key] ? keys[key] + "," : "") + value; // 提取键值对信息

            /* 
             * 返回空字符串,由于咱们确实不关注源字符串中发生的替换操做
             * 咱们只须要利用该函数的反作用,而不须要实际替换结果
             */
            return "";
        }
    );

    // 收集键的信息
    var result = [];
    for (var key in keys) {
        result.push(key + "=" + keys[key]);
    }

    // 使用&将结果进行合并
    return result.join("&");
}

var origin = "foo=1&foo=2&blah=a&blah=b&foo=3";
compress(origin);   // "foo=1,2,3&blah=a,b"
  • 一个有趣点:如何使用字符串的 replace() 方法来遍历一个字符串,而不是一个实际的搜索替换机制。

  • 两个关键点:1)传递一个函数做为替换值参数;2)该函数并非返回实际的值,而是简单地利用它做为一种搜索手段。

利用这种技巧,咱们可使用 String 对象的 replace() 方法做为字符串搜索机制。搜索结果不只快速,并且简单、有效。

经常使用正则表达式

1. 修剪字符串:
// 修剪字符串
function trim(str) {
    return (str || "").replace(/^\s+|\s+$/g, "");
}

trim(" #id div.class ");    // "#id div.class"
2. 匹配换行符:
// 匹配全部的字符,包括换行符
var html = "<b>Hello</b>\n<i>world!</i>";

// 显示换行符没有被匹配到
console.log(/.*/.exec(html)[0] === "<b>Hello</b>"); // true

// 使用空白符匹配方式匹配全部的元素,为最佳方案
console.log(/[\S\s]*/.exec(html)[0] === "<b>Hello</b>\n<i>world!</i>"); // true

// 用另外一种方式匹配全部元素
console.log(/(?:.|\s)*/.exec(html)[0] === "<b>Hello</b>\n<i>world!</i>");   // true
3. Unicode:
// 匹配 Unicode 字符
var text = "\u5FCD\u8005\u30D1\u30EF\u30FC";

var matchAll = /[w\u0080-\uFFFF_-]+/;

text.match(matchAll);   // ["忍者パワー"]
4. 转义字符:
// 在 CSS 选择器中匹配转义字符
var pattern = /^((\w+)|(\\.))+$/;   // 容许匹配一个单词字符,
                                    // 或一个反斜杠及后面跟随任意字符(甚至是另一个反斜杠)
                                    // 或者二者均可以匹配
var tests = [
    "formUpdate",   // true
    "form\\.update\\.whatever", // true
    "form\\:update",    // true
    "\\f\\o\\r\\m\\u\\p\\d\\a\\t\\e",   // true
    "form:update"   // false,未能匹配到非单词字符(:)
];

for (var n=0; n<tests.length; n++) {
    console.log(pattern.test(tests[n]));
}

参考博文:

JavaScript 正则表达式上——基本语法

JavaScript正则表达式下——相关方法

我所认识的JavaScript正则表达式

正则表达式 - 教程

解惑正则表达式中的捕获

相关文章
相关标签/搜索