ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JavaScript正则表达式入门

2019-08-18 21:57:12  阅读:225  来源: 互联网

标签:regex 匹配 入门 正则表达式 JavaScript log var console string


正则表达式

正则表达式本身是一种匹配模式,用计算机语言来描述我们需要匹配到的结构

正则表达式语法

正则表达式从匹配形式来说,要么匹配字符,要么匹配位置,以下从分别这两点展开学习

匹配字符

横向模糊匹配

正则匹配到的字符串是不固定的 可以使用量词来指定片段出现的次数,次数会影响到字符串的长度,因为称之为横向模糊匹配

示例

var regex = /ab{2,5}c/g ;   //g 含有一个a,2-5个b,一个c的字符串

var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );

// 匹配结果,只有满足前后位ac,中间只有2-5个b的字符串都匹配到了
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]

量词

指定需要匹配的字符次数,一些常见的次数表示可以用等价的符号代替,如下图:

纵向模糊匹配

表示同一个位置可以匹配多种可能的字符

示例

var regex = /a[123]b/g;  // 匹配以a开头,以b结尾,中间含有123任意其中一个的字符

var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );

// 匹配结果
// => ["a1b", "a2b", "a3b"]

字符组

常见的纵向模糊匹配集合别名

字符组

字符匹配案例分析

1.匹配日期

匹配 2017-06-10

分析

年 四位数字即可 [0-9]{4}

月,分为0开头和1开头 0[1-9] | 1[0-2]

日,分为0、1、2、3开头 0[1-9] | [12][1-9] | 3[01]

正则

var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
console.log( regex.test("2017-06-10") );
// => true

2.匹配id

分析

id="." 但是由于是贪婪匹配,就会匹配到最后一个双引号为止

id=".*?" 可以使用惰性匹配来解决,但是效率低下

id="[^"]*" 最佳

正则

var regex = /id="[^"]*"/
var string = '<div id="container" class="main"></div>';
console.log(string.match(regex)[0]);
// => id="container"

位置匹配

位置在正则中表示相邻字符之间的位置,有以下描点

^

表示字符串开头,多行字符表示行开头

$ 结尾

表示字符串开头,多行字符表示行结尾

下面我们可以把字符串的开头和结尾用'#'代替

单行

var result = "hello".replace(/^|$/g, '#');
console.log(result);

// => "#hello#"

多行

var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);

/*
#I#
#love#
#javascript#
*/

\b 单词边界

\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"

\B 非单词边界

var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
console.log(result);
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"

(?=p) 先行断言

比如 (?=l),表示 "l" 字符前面的位置,不包括p模式匹配的字符

var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"

(?!p) 是 (?=p) 取反 先行否定断言

除匹配p模式前面以外的位置

var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"

(?<=p) 后行断言

位置前面要满足匹配p模式,不包括p模式匹配的字符

var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "hel#l#o"

(?<!p) 后行否定断言

位置前面要满足匹配p模式,除此位置的其余位置

var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "#h#e#llo#"

位置匹配案例分析

1.数字千位分隔符表示法

比如把 "12345678",变成 "12,345,678"。

分析

这个匹配一看就是匹配3位数字的前面的位置,可以使用先行断言来匹配 ?=\d{3}+, 就可以做到

正则

var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => "12,345,678"

// 这里尝试3的倍数位数字会发现开头也加上了,
var result = "112345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",112,345,678"

// 限制此位置不能是开头即可
var regex = /(?!^)(?=(\d{3})+$)/g;
result = "123456789".replace(regex, ',');
console.log(result);
// => "123,456,789"

1.实现字符串trim方法

分析

trim是用来去除字符串首尾空白符,嗯,一读这句话,首尾,那不就是首尾
^ $嘛,至于空白符\s就完事了

正则

function trim(str) {
  return str.replace(/^\s+|\s+$/g, '')
}
console.log(trim(' foobar '))

trim方法是实现首尾去除空白符的,那么新推出的trimStart,trimEnd如何实现呢,嘻嘻,大家可以想想

元字符转义问题

转义这个问题是指一些符号在正则中拥有特殊的含义,比如^表示字符串起始位置,那么如何表示^这个字符串呢,那么就需要特殊的方法,成为转义,转义只需要在字符前加上\

正则表达式元字符

^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- ,  

匹配行为-贪婪匹配和惰性匹配

这里的匹配行为由将我们正则转为计算机语言的状态机决定,常见的有DFA和NFA, 目前比较常见的是NFA,JavaScript也是采用NFA实现的正则引擎,NFA一个特点就是会产生回溯行为,生动地来将,它采用类似深度优先搜索思想,遍历可能匹配的字符串,一旦下一次匹配失效,即可回退到前一个状态,听起来很像拿着线球走出迷宫的故事,回溯行为从直观想就能得知会影响效率,在JavaScript正则中,常见的回溯形式为贪婪量词、惰性量词、分支结构,下面会依次介绍

贪婪匹配

最大范围匹配

var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]

示例中会将数字尽可能匹配到,所以看到匹配的数字都是依照空白符分割开来的

惰性匹配 ?

最小匹配范围

var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]

示例中,加了?的量词之后,正则会在匹配2个数字之后停止

多选分支

子模式任选其一 属于惰性匹配具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。

var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]

括号

在很多语言语法中,括号最常见的是代表优先级,我们看看括号在正则表达式中有什么特殊的用途呢?

产生整体

/ab+/ => a接上至少一个b
如何把量词作用与一个整体 
/(ab)+/

分支结构

表达分支的可能性

表示p1或p2表达式任选其一
var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );

分组引用

用于捕获括号匹配的结果

1.提取数据

括号里的匹配字符串可以被直接引用,用以特定的场景

提取年月日

var regex = /(\d{4})-(\d{2})-(\d{2})/g;
var string = "2017-06-12";
console.log( string.match(regex) );
console.log( RegExp.$1 ); // 2017
console.log( RegExp.$2 ); // 06
console.log( RegExp.$3 ); // 12

2.替换

日期更换格式

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"

反向引用

可以引用之前出现的分组结果

有时候需要引用前面匹配的结果,比如说下面要求日期分隔符一致

// 1表示出现的一个分组中的匹配结果
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

非捕获括号

不捕获匹配的结果

var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

括号案例

// 驼峰化
function camelize (str) {
return str.replace(/[-_\s]+(.)?/g, function (match, c) {
return c ? c.toUpperCase() : '';
});
}
console.log( camelize('-moz-transform') );


// 中划线化
function dasherize (str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') );

操作符优先级

正则表达式可视化

emmmm,虽然有优先级,但是有时候还是会看不懂,使用可视化工具帮助我们理解是一个很不错的idea,这里有一个正则可视化网址,在阅读不懂的正则表达式可以助你一臂之力。

正则表达式修饰符

修饰符 描述
g 全局匹配,即找到所有的匹配
y 全局匹配,即找到所有的匹配,但是此匹配要求每个子串是连续下标的
i 忽略字母大小写
m 多行匹配,使用^和$匹配多行时使用

g

默认是找到第一个匹配字符就停止,加了g修饰符就会找到字符串中所有匹配的字符,下面的例子一目了然

var regex = /\d/;
var string = "123";
console.log( string.match(regex) );  // [ '1', index: 0, input: '123' ]

var regex = /\d/g;
var string = "123";
console.log( string.match(regex) );  // [ '1', '2', '3' ]

y

与g行为一致的是,找到全局匹配的子串,但是y有特殊行为,要求每一个匹配的子串起始位置必须是上一个子串的结束位置,看一下下面的例子

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

i

i的含义比较简单

var regex = /\A/i;
var string = "a";
console.log( string.match(regex) ); // [ 'a', index: 0, input: 'a' ]

var regex = /\A/i;
var string = "A";
console.log( string.match(regex) ); // [ 'A', index: 0, input: 'A' ]

m

m就是为了让^和$变成行头和行尾

var regex = /^A$/g;
var string = "A\nA";
console.log( string.match(regex) ); // null

var regex = /^A$/mg;
var string = "A\nA";
console.log( string.match(regex) ); // [ 'A', 'A' ]

正则表达式编程

在我们使用正则表达式匹配之后,JavaScript提供了一些操作给我们使用,下面依次介绍

起始API exec

exec是正则表达式编程中最基本的API,它拥有对字符串匹配,迭代的能力,后续API可以理解为特定场景的exec封装的API,在默认情况下返回第一次匹配的字符串,g修饰符下,每次从上一个匹配结果末端下标开始查找下一个匹配结果

/**
方法返回正则匹配的字符串
* @param string 执行的字符串
* @return {RegExpExecArray | null} 正则执行数组或者null
*/
exec(string: string): RegExpExecArray | null;
let reg = /\d/g;
let s = "123456"
console.log(reg.exec(s)); // [ '1', index: 0, input: '123456' ]
console.log(reg.exec(s)); // [ '2', index: 1, input: '123456' ]

验证

检测目标字符串是否有满足匹配的子串,注意验证是有没有子串,比较常用的是test,它直接返回boolean表示验证结果

 /**
  方法返回一个布尔值表示是否在给定字符串中,存在一个匹配正则的字符串
  * @param string 被测试的字符串
  * @return {boolean} 是否匹配存在匹配子串
*/
test(string: string): boolean;

可以理解成一下代码

// 若有一个或多个匹配,第一次匹配都能匹配到结果,否则返回null
RegExp.prototype.test = function (str) {
  return !!this.exec(str)
}

let reg = /\d/;
let s = "123456"
console.log(reg.test(s)); // true

实例

var regex = /\d/;
var string = "abc123";
console.log( regex.test(string) ); // true

切分

在匹配标志符位置进行切分

/**
  方法按照给定的正则返回分割后的子串数组
  * @param separator 分割器,可以使一个字符串或者一个正则表达式 
  * @param limit 返回结果数组的长度
  * @return {string []} 分割后的字符串数组
  */
split(separator: string | RegExp, limit?: number): string[];

可以理解成以下代码

String.prototype.split = function(reg, limit) {
  let curIndex = -1
  // 此时this为String对象,需要拆包
  let str =  this.valueOf()
  let splitArr = []
  // 执行exec方法
  while(result = reg.exec(str)) {
    let findIndex = result.index 
    // 分隔符相邻
    const isadjoin =  curIndex + 1 === findIndex
    // 分隔符之间的字符
    let splitMiddleStr = str.substring(curIndex + 1, findIndex)
    // 此次分割出来的字符
    let splitedStr = isadjoin ? "" :  splitMiddleStr
    splitArr.push(splitedStr)
    curIndex = findIndex
  }
  return splitArr.slice(limit)
}

实例

var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex) );
// => ["html", "css", "javascript"]

var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex, 1) );
// => ["html"]

注意点

1.使用正则切分,若正则含有捕获括号, 结果会带有正则匹配部分

var string = "html,css,javascript";
console.log( string.split(/(,)/) );
// =>["html", ",", "css", ",", "javascript"]

提取

匹配后提取部分数据,比较常用的是match

/**
* 方法会以给定的正则去匹配字符串,若匹配成功,则返回匹配数组,否则返回null
* @param regexp 字符串或者正则对象
* @return {RegExpMatchArray | null} 匹配结果数组或者null
*/
match(regexp: string | RegExp): RegExpMatchArray | null;

可以理解成以下代码

var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;

String.prototype.match = function (reg) {
  let str = this.valueOf()
  // 是否含有g修饰符
  let isGlobal = reg.global
  let result = []
  let curString = ""
  if(isGlobal) {
    // 返回全部匹配字符串数组
    while(curString = reg.exec(str)) {
      result.push(curString[1])
    }
  }else {
    // 返回第一次匹配的字符串数组
    result = reg.exec(str)
  }
  return result
}
console.log(string.match(regex1)); // [ '2017', '2017', index: 0, input: '2017.06.27' ]
console.log(string.match(regex2)); // [ '2017', '06', '27' ]

实例


var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

注意点

1.match会把第一个参数的字符串转成正则

var string = "2017.06.27";
console.log( string.match(".") );
// => ["2", index: 0, input: "2017.06.27"]
//需要修改成下列形式之一
console.log( string.match("\\.") );
console.log( string.match(/\./) );
// => [".", index: 4, input: "2017.06.27"]
// => [".", index: 4, input: "2017.06.27"]

2.match返回值

var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
console.log( string.match(regex1) );
// => ["2017", "2017", index: 0, input: "2017.06.27"]  不带g返回第一个匹配的字符串,分组捕获的内容,整体匹配的第一个下标,输入的目标字符串

console.log( string.match(regex2) );  // 带g返回所有匹配的内容
// => ["2017", "06", "27"]

替换

替换匹配的信息进行处理,使用replace

/**
* 方法按照匹配规则, 使用替换值,将匹配字符串替换为替换值
* @param searchValue 需要匹配的字符串或者正则
* @param replaceValue 替换字符串
* @return {string} 替换后的字符串
*/
replace(searchValue: string | RegExp, replaceValue: string): string;

/**
* 方法按照匹配规则, 
* @param searchValue 需要匹配的字符串或者正则
* @param replacer 替换器
* @return {string} 替换后的字符串
*/
replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;

可以理解成以下代码,这里仅实现全局替换,让大家了解replace是一个特定场景下的api即可

var string = "2017.06.27";
var regex2 = /\d+/g;

String.prototype.replace = function (reg, replaceValue) {
  // 这里this是String对象,使用valueOf取出字符串值
  let str = this.valueOf()
  // 先用正则分割字符串
  let strArr = str.split(reg)
  // 用replaceValue来填充替换的地方
  str = strArr.join(replaceValue)
  return str
}
console.log(string.replace(regex2, "1")); // 1.1.1

实例

var string = "2017-06-26";
var today = new Date( string.replace(/-/g, "/") );
console.log( today );
// => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)

注意点

1.当第二个参数是字符串是,有以下特殊字符

2.当第二个参数是函数时,函数传入参数

// 从左往右分别是匹配的字符串,捕获组,匹配字符串的起始位置,输入字符串
[match, $1, $2, index, input]

let  a = "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
  console.log([match, $1, $2, index, input]);
});
// => ["1234", "1", "4", 0, "1234 2345 3456"]
// => ["2345", "2", "5", 5, "1234 2345 3456"]
// => ["3456", "3", "6", 10, "1234 2345 3456"]

3.replace可以嵌套使用

在一些需求里,我们无可避免需要多次正则处理,比如先找到一个整体,再替换这个整体里面的一部分,以缩小影响范围,可以先用replace匹配一次子串,然后再次缩小范围匹配

let domStr = `<div style="font-family: Times, serif, 'Times New Roman'" class="333">
    <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333">
      <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div>
    </div>
</div>`
// 匹配style="" 关键是中间部分
let styleRegex = /style="[^"]*"/g  
let result = domStr.replace(styleRegex, function(style) {
  console.log(style);
   let isoffFontFamily = /font\-family\:([^;])*(Times New Roman)+([^;"])*/;
   return style.replace(isoffFontFamily, "");
})

console.log(result);
// <div style="" class="333">
//     <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333">
//       <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div>
//     </div>
// </div>

构造函数与实例

1.一般不使用构造函数生成正则表达式,优先使用字面量,除非需要动态生成正则表达式

2.修饰符都有其对应的对象布尔属性表面是否启用

修饰符 实例属性
g global
y sticky
i ingnoreCase
m multiline

3.可以用过source实例属性来查看动态构建的正则表达式结果

var className = "high";
var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
console.log( regex.source )
// => (^|\s)high(\s|$) 即字符串"(^|\\s)high(\\s|$)"

正则表达式的构建

如何针对问题,构建一个合适的正则表达式

法则

1.是否有必要使用正则

人在学习一个新东西之后,很容易陷入到无所不用,正则亦是如此,其实很多时候,我们能够用字符串API解决,就不需要正则出手,以下是一些例子

var string = "2017-07-01";
var regex = /^(\d{4})-(\d{2})-(\d{2})/;
console.log( string.match(regex) );
// => ["2017-07-01", "2017", "07", "01", index: 0, input: "2017-07-01"]

var result = string.split("-");
console.log( result );
// => ["2017", "07", "01"]

在年月日的例子中,我们使用了正则来获取年月日,相比采用分隔符,增加了代码的复杂性。

2.是否有必要严格匹配

一般来说,正则由于其复杂性,复杂度上来,需要严格匹配就很困难,应该结合场景,够用就行,或者可以做一些预处理字符串,使得匹配难度降低。

3.效率

1.尽量使用具体字符组代替通配符,减少回溯

/.*/ 虽然可以匹配任意字符,但是因为贪婪性质,容易出现回溯行为,比如下面的
匹配"abc" 使用/".*"/
// 123"abc"456 

**在匹配过程中进行了4次回溯,由于回溯需要保存之前的状态,所以需要额外的空间,另外,回溯行为很直观地可以看出来影响效率,/"[^"]*"/,就可以避免回溯行为**

2.独立出确定字符,加快匹配判断速度

/a+/ 
// 如果能确定是比如字符a是存在的,可以改写成一下正则,加快匹配判断速度
/aa+/

3.对多选分支提取公共部分,减少匹配过程中可消除的重复

/^abc|^def/ //修改成 
/^(?:abc|def)/。
// 多选分支是惰性的,在不匹配分支的时候,会尝试其他分支,公共的部分也会进行这个匹配,然而公共的部分没有必要多次匹配,可以提取出来,减少匹配中的重复过程

4.使用非捕获括号

在我们不需要捕获括号中的内容时,可以使用非捕获括号来省掉原本用户保存捕获内容的内存

实践

1.快速找出Vue源码中的所有正则

尝试一

/\/.*\//

// 这会把注释、路径、域名都匹配进去

// /* */  

结果

尝试2

/\/(\S)+\//

// 这里匹配了非空字符串,而且必须至少出现一次

// /* */  

结果

一下子少了很多,但是仍然会匹配到不是正则的 /ggg/ 可能只是路径中的一部分,但是我们需要检查的量已经可以接受了。

标签:regex,匹配,入门,正则表达式,JavaScript,log,var,console,string
来源: https://www.cnblogs.com/sefaultment/p/11374103.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有