第七章 正则表达式编程

Posted 程序猿DD

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第七章 正则表达式编程相关的知识,希望对你有一定的参考价值。


第七章 正则表达式编程

什么叫知识,能指导我们实践的东西才叫知识。

学习一样东西,如果不能使用,最多只能算作纸上谈兵。正则表达式的学习,也不例外。

掌握了正则表达式的语法后,下一步,也是关键的一步,就是在真实世界中使用它。

那么如何使用正则表达式呢?有哪些关键的点呢?本章就解决这个问题。

内容包括:

  1. 正则表达式的四种操作

  2. 相关API注意要点

  3. 真实案例

1. 正则表达式的四种操作

正则表达式是匹配模式,不管如何使用正则表达式,万变不离其宗,都需要先“匹配”。

有了匹配这一基本操作后,才有其他的操作:验证、切分、提取、替换。

进行任何相关操作,也需要宿主引擎相关API的配合使用。当然,在JS中,相关API也不多。

1.1 验证

验证是正则表达式最直接的应用,比如表单验证。

在说验证之前,先要说清楚匹配是什么概念。

所谓匹配,就是看目标字符串里是否有满足匹配的子串。因此,“匹配”的本质就是“查找”。

有没有匹配,是不是匹配上,判断是否的操作,即称为“验证”。

这里举一个例子,来看看如何使用相关API进行验证操作的。

比如,判断一个字符串中是否有数字。

  • 使用 search

 
   
   
 
  1. var regex = /\d/;

  2. var string = "abc123";

  3. console.log( !!~string.search(regex) );

  4. // => true

  • 使用 test

 
   
   
 
  1. var regex = /\d/;

  2. var string = "abc123";

  3. console.log( regex.test(string) );

  4. // => true

  • 使用 match

 
   
   
 
  1. var regex = /\d/;

  2. var string = "abc123";

  3. console.log( !!string.match(regex) );

  4. // => true

  • 使用 exec

 
   
   
 
  1. var regex = /\d/;

  2. var string = "abc123";

  3. console.log( !!regex.exec(string) );

  4. // => true

其中,最常用的是 test

1.2 切分

匹配上了,我们就可以进行一些操作,比如切分。

所谓“切分”,就是把目标字符串,切成一段一段的。在JS中使用的是 split

比如,目标字符串是"html,css,javascript",按逗号来切分:

 
   
   
 
  1. var regex = /,/;

  2. var string = "html,css,javascript";

  3. console.log( string.split(regex) );

  4. // => ["html", "css", "javascript"]

又比如,如下的日期格式:

2017/06/26

2017.06.26

2017-06-26

可以使用 split“切出”年月日:

 
   
   
 
  1. var regex = /\D/;

  2. console.log( "2017/06/26".split(regex) );

  3. console.log( "2017.06.26".split(regex) );

  4. console.log( "2017-06-26".split(regex) );

  5. // => ["2017", "06", "26"]

  6. // => ["2017", "06", "26"]

  7. // => ["2017", "06", "26"]

1.3 提取

虽然整体匹配上了,但有时需要提取部分匹配的数据。

此时正则通常要使用分组引用(分组捕获)功能,还需要配合使用相关API。

这里,还是以日期为例,提取出年月日。注意下面正则中的括号:

  • match

 
   
   
 
  1. var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;

  2. var string = "2017-06-26";

  3. console.log( string.match(regex) );

  4. // =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

  • exec

 
   
   
 
  1. var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;

  2. var string = "2017-06-26";

  3. console.log( regex.exec(string) );

  4. // =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

  • test

 
   
   
 
  1. var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;

  2. var string = "2017-06-26";

  3. regex.test(string);

  4. console.log( RegExp.$1, RegExp.$2, RegExp.$3 );

  5. // => "2017" "06" "26"

  • search

 
   
   
 
  1. var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;

  2. var string = "2017-06-26";

  3. string.search(regex);

  4. console.log( RegExp.$1, RegExp.$2, RegExp.$3 );

  5. // => "2017" "06" "26"

  • replace

 
   
   
 
  1. var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;

  2. var string = "2017-06-26";

  3. var date = [];

  4. string.replace(regex, function(match, year, month, day) {

  5.    date.push(year, month, day);

  6. });

  7. console.log(date);

  8. // => ["2017", "06", "26"]

其中,最常用的是 match

1.4 替换

找,往往不是目的,通常下一步是为了替换。在JS中,使用 replace进行替换。

比如把日期格式,从yyyy-mm-dd替换成yyyy/mm/dd:

 
   
   
 
  1. var string = "2017-06-26";

  2. var today = new Date( string.replace(/-/g, "/") );

  3. console.log( today );

  4. // => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)

这里只是简单地应用了一下 replace。但, replace方法是强大的,是需要重点掌握的。

2. 相关API注意要点

从上面可以看出用于正则操作的方法,共有6个,字符串实例4个,正则实例2个:

String#search

String#split

String#match

String#replace

RegExp#test

RegExp#exec

本文不打算详细地讲解它们的方方面面细节,具体可以参考《JavaScript权威指南》的第三部分。本文重点列出一些容易忽视的地方,以飨读者。

2.1 search和match的参数问题

我们知道字符串实例的那4个方法参数都支持正则和字符串。

searchmatch,会把字符串转换为正则的。

 
   
   
 
  1. var string = "2017.06.27";

  2. console.log( string.search(".") );

  3. // => 0

  4. //需要修改成下列形式之一

  5. console.log( string.search("\\.") );

  6. console.log( string.search(/\./) );

  7. // => 4

  8. // => 4

  9. console.log( string.match(".") );

  10. // => ["2", index: 0, input: "2017.06.27"]

  11. //需要修改成下列形式之一

  12. console.log( string.match("\\.") );

  13. console.log( string.match(/\./) );

  14. // => [".", index: 4, input: "2017.06.27"]

  15. // => [".", index: 4, input: "2017.06.27"]

  16. console.log( string.split(".") );

  17. // => ["2017", "06", "27"]

  18. console.log( string.replace(".", "/") );

  19. // => "2017/06.27"

2.2 match返回结果的格式问题

match返回结果的格式,与正则对象是否有修饰符 g有关。

 
   
   
 
  1. var string = "2017.06.27";

  2. var regex1 = /\b(\d+)\b/;

  3. var regex2 = /\b(\d+)\b/g;

  4. console.log( string.match(regex1) );

  5. console.log( string.match(regex2) );

  6. // => ["2017", "2017", index: 0, input: "2017.06.27"]

  7. // => ["2017", "06", "27"]

没有 g,返回的是标准匹配格式,即,数组的第一个元素是整体匹配的内容,接下来是分组捕获的内容,然后是整体匹配的第一个下标,最后是输入的目标字符串。

g,返回的是所有匹配的内容。

当没有匹配时,不管有无 g,都返回 null

2.3 exec比match更强大

当正则没有 g时,使用 match返回的信息比较多。但是有 g后,就没有关键的信息 index了。

exec方法就能解决这个问题,它能接着上一次匹配后继续匹配:

 
   
   
 
  1. var string = "2017.06.27";

  2. var regex2 = /\b(\d+)\b/g;

  3. console.log( regex2.exec(string) );

  4. console.log( regex2.lastIndex);

  5. console.log( regex2.exec(string) );

  6. console.log( regex2.lastIndex);

  7. console.log( regex2.exec(string) );

  8. console.log( regex2.lastIndex);

  9. console.log( regex2.exec(string) );

  10. console.log( regex2.lastIndex);

  11. // => ["2017", "2017", index: 0, input: "2017.06.27"]

  12. // => 4

  13. // => ["06", "06", index: 5, input: "2017.06.27"]

  14. // => 7

  15. // => ["27", "27", index: 8, input: "2017.06.27"]

  16. // => 10

  17. // => null

  18. // => 0

其中正则实例 lastIndex属性,表示下一次匹配开始的位置。

比如第一次匹配了“2017”,开始下标是0,共4个字符,因此这次匹配结束的位置是3,下一次开始匹配的位置是4。

从上述代码看出,在使用 exec时,经常需要配合使用 while循环:

 
   
   
 
  1. var string = "2017.06.27";

  2. var regex2 = /\b(\d+)\b/g;

  3. var result;

  4. while ( result = regex2.exec(string) ) {

  5.    console.log( result, regex2.lastIndex );

  6. }

  7. // => ["2017", "2017", index: 0, input: "2017.06.27"] 4

  8. // => ["06", "06", index: 5, input: "2017.06.27"] 7

  9. // => ["27", "27", index: 8, input: "2017.06.27"] 10

2.4 修饰符g,对exex和test的影响

上面提到了正则实例的 lastIndex属性,表示尝试匹配时,从字符串的 lastIndex位开始去匹配。

字符串的四个方法,每次匹配时,都是从0开始的,即 lastIndex属性始终不变。

而正则实例的两个方法 exectest,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex。下面让我们以 test为例,看看你是否会迷糊:

 
   
   
 
  1. var regex = /a/g;

  2. console.log( regex.test("a"), regex.lastIndex );

  3. console.log( regex.test("aba"), regex.lastIndex );

  4. console.log( regex.test("ababc"), regex.lastIndex );

  5. // => true 1

  6. // => true 3

  7. // => false 0

注意上面代码中的第三次调用 test,因为这一次尝试匹配,开始从下标 lastIndex即3位置处开始查找,自然就找不到了。

如果没有 g,自然都是从字符串第0个字符处开始尝试匹配:

 
   
   
 
  1. var regex = /a/;

  2. console.log( regex.test("a"), regex.lastIndex );

  3. console.log( regex.test("aba"), regex.lastIndex );

  4. console.log( regex.test("ababc"), regex.lastIndex );

  5. // => true 0

  6. // => true 0

  7. // => true 0

2.5 test整体匹配时需要使用^和$

这个相对容易理解,因为 test是看目标字符串中是否有子串匹配正则,即有部分匹配即可。

如果,要整体匹配,正则前后需要添加开头和结尾:

 
   
   
 
  1. console.log( /123/.test("a123b") );

  2. // => true

  3. console.log( /^123$/.test("a123b") );

  4. // => false

  5. console.log( /^123$/.test("123") );

  6. // => true

2.6 split相关注意事项

split方法看起来不起眼,但要注意的地方有两个的。

第一,它可以有第二个参数,表示结果数组的最大长度:

 
   
   
 
  1. var string = "html,css,javascript";

  2. console.log( string.split(/,/, 2) );

  3. // =>["html", "css"]

第二,正则使用分组时,结果数组中是包含分隔符的:

 
   
   
 
  1. var string = "html,css,javascript";

  2. console.log( string.split(/(,)/) );

  3. // =>["html", ",", "css", ",", "javascript"]

2.7 replace是很强大的

《JavaScript权威指南》认为 exec是这6个API中最强大的,而我始终认为 replace才是最强大的。因为它也能拿到该拿到的信息,然后可以假借替换之名,做些其他事情。

总体来说 replace有两种使用形式,这是因为它的第二个参数,可以是字符串,也可以是函数。

当第二个参数是字符串时,如下的字符有特殊的含义:

$1, $2,..., $99匹配第1~99个分组里捕获的文本$& 匹配到的子串文本$``匹配到的子串的左边文本$' 匹配到的子串的右边文本$$` 美元符号

例如,把"2,3,5",变成"5=2+3":

 
   
   
 
  1. var result = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2");

  2. console.log(result);

  3. // => "5=2+3"

又例如,把"2,3,5",变成"222,333,555":

 
   
   
 
  1. var result = "2,3,5".replace(/(\d+)/g, "$&$&$&");

  2. console.log(result);

  3. // => "222,333,555"

再例如,把"2+3=5",变成"2+3=2+3=5=5":

 
   
   
 
  1. var result = "2+3=5".replace(/=/, "$&$`$&$'$&");

  2. console.log(result);

  3. // => "2+3=2+3=5=5"

当第二个参数是函数时,我们需要注意该回调函数的参数具体是什么:

 
   
   
 
  1. "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function(match, $1, $2, index, input) {

  2.    console.log([match, $1, $2, index, input]);

  3. });

  4. // => ["1234", "1", "4", 0, "1234 2345 3456"]

  5. // => ["2345", "2", "5", 5, "1234 2345 3456"]

  6. // => ["3456", "3", "6", 10, "1234 2345 3456"]

此时我们可以看到 replace拿到的信息,并不比 exec少。

2.8 使用构造函数需要注意的问题

一般不推荐使用构造函数生成正则,而应该优先使用字面量。因为用构造函数会多写很多 \

 
   
   
 
  1. var string = "2017-06-27 2017.06.27 2017/06/27";

  2. var regex = /\d{4}(-|\.|\/)\d{2}\1\d{2}/g;

  3. console.log( string.match(regex) );

  4. // => ["2017-06-27", "2017.06.27", "2017/06/27"]

  5. regex = new RegExp("\\d{4}(-|\\.|\\/)\\d{2}\\1\\d{2}", "g");

  6. console.log( string.match(regex) );

  7. // => ["2017-06-27", "2017.06.27", "2017/06/27"]

2.9 修饰符

ES5中修饰符,共3个:

g 全局匹配,即找到所有匹配的,单词是global

i 忽略字母大小写,单词ingoreCase

m 多行匹配,只影响 ^$,二者变成行的概念,即行开头和行结尾。单词是multiline

当然正则对象也有相应的只读属性:

 
   
   
 
  1. var regex = /\w/img;

  2. console.log( regex.global );

  3. console.log( regex.ignoreCase );

  4. console.log( regex.multiline );

  5. // => true

  6. // => true

  7. // => true

2.10 source属性

正则实例对象属性,除了 globalingnoreCasemultilinelastIndex属性之外,还有一个 source属性。

它什么时候有用呢?

比如,在构建动态的正则表达式时,可以通过查看该属性,来确认构建出的正则到底是什么:

 
   
   
 
  1. var className = "high";

  2. var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");

  3. console.log( regex.source )

  4. // => (^|\s)high(\s|$) 即字符串"(^|\\s)high(\\s|$)"

2.11 构造函数属性

构造函数的静态属性基于所执行的最近一次正则操作而变化。除了是 $1,..., $9之外,还有几个不太常用的属性(有兼容性问题):

RegExp.input 最近一次目标字符串,简写成 RegExp["$_"]RegExp.lastMatch 最近一次匹配的文本,简写成 RegExp["$&"]RegExp.lastParen 最近一次捕获的文本,简写成 RegExp["$+"]RegExp.leftContext 目标字符串中 lastMatch之前的文本,简写成 RegExp["$"] RegExp.rightContext 目标字符串中lastMatch 之后的文本,简写成RegExp["$'"]`

测试代码如下:

 
   
   
 
  1. var regex = /([abc])(\d)/g;

  2. var string = "a1b2c3d4e5";

  3. string.match(regex);

  4. console.log( RegExp.input );

  5. console.log( RegExp["$_"]);

  6. // => "a1b2c3d4e5"

  7. console.log( RegExp.lastMatch );

  8. console.log( RegExp["$&"] );

  9. // => "c3"

  10. console.log( RegExp.lastParen );

  11. console.log( RegExp["$+"] );

  12. // => "3"

  13. console.log( RegExp.leftContext );

  14. console.log( RegExp["$`"] );

  15. // => "a1b2"

  16. console.log( RegExp.rightContext );

  17. console.log( RegExp["$'"] );

  18. // => "d4e5"

3. 真实案例

3.1 使用构造函数生成正则表达式

我们知道要优先使用字面量来创建正则,但有时正则表达式的主体是不确定的,此时可以使用构造函数来创建。模拟 getElementsByClassName方法,就是很能说明该问题的一个例子。

这里 getElementsByClassName函数的实现思路是:

  • 比如要获取className为"high"的dom元素;

  • 首先生成一个正则: /(^|\s)high(\s|$)/

  • 然后再用其逐一验证页面上的所有dom元素的类名,拿到满足匹配的元素即可。

代码如下(可以直接复制到本地查看运行效果):

 
   
   
 
  1. <p class="high">1111</p>

  2. <p class="high">2222</p>

  3. <p>3333</p>

  4. <script>

  5. function getElementsByClassName(className) {

  6.    var elements = document.getElementsByTagName("*");

  7.    var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");

  8.    var result = [];

  9.    for (var i = 0; i < elements.length; i++) {

  10.        var element = elements[i];

  11.        if (regex.test(element.className)) {

  12.            result.push(element)

  13.        }

  14.    }

  15.    return result;

  16. }

  17. var highs = getElementsByClassName('high');

  18. highs.forEach(function(item) {

  19.    item.style.color = 'red';

  20. });

  21. </script>

3.2 使用字符串保存数据

一般情况下,我们都愿意使用数组来保存数据。但我看到有的框架中,使用的却是字符串。

使用时,仍需要把字符串切分成数组。虽然不一定用到正则,但总感觉酷酷的,这里分享如下:

 
   
   
 
  1. var utils = {};

  2. "Boolean|Number|String|Function|Array|Date|RegExp|Object|Error".split("|").forEach(function(item) {

  3.    utils["is" + item] = function(obj) {

  4.        return {}.toString.call(obj) == "[object " + item + "]";

  5.    };

  6. });

  7. console.log( utils.isArray([1, 2, 3]) );

  8. // => true

3.3 if语句中使用正则替代&&

比如,模拟 ready函数,即加载完毕后再执行回调(不兼容ie的):

 
   
   
 
  1. var readyRE = /complete|loaded|interactive/;

  2. function ready(callback) {

  3.    if (readyRE.test(document.readyState) && document.body) {

  4.        callback()

  5.    }

  6.    else {

  7.        document.addEventListener(

  8.            'DOMContentLoaded',

  9.            function () {

  10.                callback()

  11.            },

  12.            false

  13.        );

  14.    }

  15. };

  16. ready(function() {

  17.    alert("加载完毕!")

  18. });

3.4 使用强大的replace

因为 replace方法比较强大,有时用它根本不是为了替换,只是拿其匹配到的信息来做文章。

这里以查询字符串(querystring)压缩技术为例,注意下面 replace方法中,回调函数根本没有返回任何东西。

 
   
   
 
  1. function compress(source) {

  2.    var keys = {};

  3.    source.replace(/([^=&]+)=([^&]*)/g, function(full, key, value) {

  4.        keys[key] = (keys[key] ? keys[key] + ',' : '') + value;

  5.    });

  6.    var result = [];

  7.    for (var key in keys) {

  8.        result.push(key + '=' + keys[key]);

  9.    }

  10.    return result.join('&');

  11. }

  12. console.log( compress("a=1&b=2&a=3&b=4") );

  13. // => "a=1,3&b=2,4"

3.5 综合运用

最后这里再做个简单实用的正则测试器。

具体效果如下:

第七章 正则表达式编程

代码,直接贴了,相信你能看得懂:

 
   
   
 
  1. <section>

  2.    <div id="err"></div>

  3.    <input id="regex" placeholder="请输入正则表达式">

  4.    <input id="text" placeholder="请输入测试文本">

  5.    <button id="run">测试一下</button>

  6.    <div id="result"></div>

  7. </section>

  8. <style>

  9. section{

  10.    display:flex;

  11.    flex-direction:column;

  12.    justify-content:space-around;

  13.    height:300px;

  14.    padding:0 200px;

  15. }

  16. section *{

  17.    min-height:30px;

  18. }

  19. #err {

  20.    color:red;

  21. }

  22. #result{

  23.    line-height:30px;

  24. }

  25. .info {

  26.    background:#00c5ff;

  27.    padding:2px;

  28.    margin:2px;

  29.    display:inline-block;

  30. }

  31. </style>

  32. <script>

  33. (function() {

  34.    // 获取相应dom元素

  35.    var regexInput = document.getElementById("regex");

  36.    var textInput = document.getElementById("text");

  37.    var runBtn = document.getElementById("run");

  38.    var errBox = document.getElementById("err");

  39.    var resultBox = document.getElementById("result");

  40.    // 绑定点击事件

  41.    runBtn.onclick = function() {

  42.        // 清除错误和结果

  43.        errBox.innerHTML = "";

  44.        resultBox.innerHTML = "";

  45.        // 获取正则和文本

  46.        var text = textInput.value;

  47.        var regex = regexInput.value;

  48.        if (regex == "") {

  49.            errBox.innerHTML = "请输入正则表达式";

  50.        } else if (text == "") {

  51.            errBox.innerHTML = "请输入测试文本";

  52.        } else {

  53.            regex = createRegex(regex);

  54.            if (!regex) return;

  55.            var result, results = [];

  56.            // 没有修饰符g的话,会死循环

  57.            if (regex.global) {

  58.                while(result = regex.exec(text)) {

  59.                    results.push(result);

  60.                }

  61.            } else {

  62.                results.push(regex.exec(text));

  63.            }

  64.            if (results[0] == null) {

  65.                resultBox.innerHTML = "匹配到0个结果";

  66.                return;

  67.            }

  68.            // 倒序是有必要的

  69.            for (var i = results.length - 1; i >= 0; i--) {

  70.                var result = results[i];

  71.                var match = result[0];

  72.                var prefix = text.substr(0, result.index);

  73.                var suffix = text.substr(result.index + match.length);

  74.                text = prefix

  75.                    + '<span class="info">'

  76.                    + match

  77.                    + '</span>'

  78.                    + suffix;

  79.            }

  80.            resultBox.innerHTML = "匹配到" + results.length + "个结果:<br>" + text;

  81.        }

  82.    };

  83.    // 生成正则表达式,核心函数

  84.    function createRegex(regex) {

  85.        try {

  86.            if (regex[0] == "/") {

  87.                regex = regex.split("/");

  88.                regex.shift();

  89.                var flags = regex.pop();

  90.                regex = regex.join("/");

  91.                regex = new RegExp(regex, flags);

  92.            } else {

  93.                regex = new RegExp(regex, "g");

  94.            }

  95.            return regex;

  96.        } catch(e) {

  97.            errBox.innerHTML = "无效的正则表达式";

  98.            return false;

  99.        }

  100.    }

  101. })();

  102. </script>

小结

相关API的注意点,本章基本上算是一网打尽了。

至于文中的例子,都是点睛之笔,没有详细解析。如有理解不透的,建议自己敲一敲。

原文:https://juejin.im/post/5965943ff265da6c30653879

本文版权归作者所有,转载请经得作者授权



相关阅读          







好书推荐            


第七章 正则表达式编程

长按指纹

一键关注

以上是关于第七章 正则表达式编程的主要内容,如果未能解决你的问题,请参考以下文章

第七章 Shell正则应用

第七章 Shell正则表达式

第七章 Shell正则应用

通过 Java 正则表达式提取 semver 版本字符串的片段

Linux学习第七课-文本处理工具及正则表达式

text 正则表达式片段