乐趣区

Javascript正则表达式入门

前言

PS:2018/03/27 优化文章格式, 新增部分测试代码
说起正则其实大家都会经常接触到,前端小到校验,大到插件随处可见,简单的方法也能实现需求,不过缺乏灵活性,一旦需要复杂化,没有什么比正则更加合适的技术了。这也算是程序员的一道槛了。
以下资料截图都来源于 JavaScript RegExp 对象

new RegExp(pattern, attributes)

表示正则表达式,它是对字符串执行模式匹配的强大工具。

参数 作用
pattern 字符串,指定了正则表达式的模式或其他正则表达式。
attributes 修饰符。ECMAScript 标准化之前,不支持 m 属性。如果 pattern 是正则表达式,而不是字符串,则必须省略该参数。

返回值:
一个新的 RegExp 对象,具有指定的模式和标志。如果参数 pattern 是正则表达式而不是字符串,那么 RegExp() 构造函数将用与指定的 RegExp 相同的模式和标志创建一个新的 RegExp 对象。
如果不用 new 运算符,而将 RegExp() 作为函数调用,那么它的行为与用 new 运算符调用时一样,只是当 pattern 是正则表达式时,它只返回 pattern,而不再创建一个新的 RegExp 对象。

抛出:
SyntaxError – 如果 pattern 不是合法的正则表达式,或 attributes 含有 “g”、”i” 和 “m” 之外的字符,抛出该异常。
TypeError – 如果 pattern 是 RegExp 对象,但没有省略 attributes 参数,抛出该异常。

RegExp 对象方法

RegExpObject.compile(regexp,modifier)

用于在脚本执行过程中编译正则表达式, 也可用于改变和重新编译正则表达式(将正则表达式编译为内部格式,从而更快地执行)。

参数 作用
regexp 正则表达式
modifier 规定匹配的类型。”g” 用于全局匹配,”i” 用于区分大小写,”gi” 用于全局区分大小写的匹配。

实际上 compile 就是个修改编译作用, 不能直接用于匹配正则

var str1 = "Hello World",
  str2 = "Hello World",
  patt = /man/g;
// 正常正则
console.time();
str1 = str1.replace(patt, "person");
console.timeEnd();
// 编译后的正则
console.time();
patt.compile(patt);
str2 = str2.replace(patt, "person");
console.timeEnd();

// default: 2.818ms
// default: 0.096ms

性能提升还是挺明显的

RegExpObject.exec(string)

用于检索字符串中的正则表达式的匹配。

参数 作用
string 必需。要检索的字符串。

返回值: 返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
说明: 调用 RegExp 对象 r 的 test() 方法,并为它传递字符串 s,与这个表示式是等价的:r.exec(s) != null ---> r.test(s)
重要事项: 如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0。
返回值: 请注意,无论 RegExpObject 是否是全局模式,exec() 都会把完整的细节添加到它返回的数组中。这就是 exec() 与 match() 的不同之处,后者在全局模式下返回的信息要少得多。因此我们可以这么说,在循环中反复地调用 exec() 方法是唯一一种获得全局模式的完整模式匹配信息的方法。

var str = "Every man in the world! Every woman on earth!",
  patt = /man/g,
  result;

while ((result = patt.exec(str)) != null) {console.log(result);
  console.log(patt.lastIndex);
}

// [ 'man',
//   index: 6,
//   input: 'Every man in the world! Every woman on earth!',
//   groups: undefined ]
// 9
// [ 'man',
//   index: 32,
//   input: 'Every man in the world! Every woman on earth!',
//   groups: undefined ]
// 35


index 属性声明的是匹配文本的位置。input 属性则存放的是被检索的字符串 string。

RegExpObject.test(string)

用于检测一个字符串是否匹配某个模式.

参数 作用
string 必需。要检索的字符串。

返回值: 如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。
说明: 调用 RegExp 对象 r 的 test() 方法,并为它传递字符串 s,与这个表示式是等价的:r.exec(s) != null ---> r.test(s)

var str = "Every man in the world! Every woman on earth!",
  patt = /man/g;

console.log(patt.test(str)); // true

支持正则表达式的 String 对象方法

stringObject.search(regexp)

用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。

参数 作用
regexp 该参数可以是需要在 stringObject 中检索的子串,也可以是需要检索的 RegExp 对象。

返回值: stringObject 中第一个与 regexp 相匹配的子串的起始位置,如果没有找到任何匹配的子串,则返回 -1。
说明: search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置。

var str = "How Are you doing today?How Are you doing today?"
console.log(str.search(/are/gi)); // 4

stringObject.match(searchvalue/regexp)

可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

参数 作用
searchvalue 必需。规定要检索的字符串值。
regexp 必需。规定要匹配的模式的 RegExp 对象。如果该参数不是 RegExp 对象,则需要首先把它传递给 RegExp 构造函数,将其转换为 RegExp 对象。

返回值: 存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。
说明: match() 方法将检索字符串 stringObject,以找到一个或多个与 regexp 匹配的文本。这个方法的行为在很大程度上有赖于 regexp 是否具有标志 g。
如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本,match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。
如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。
注意: 在全局检索模式下,match() 即不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用 RegExp.exec()。

var str = "How are you doing today?How are you doing today?"
console.log(str.match(/ a/));
console.log(str.match(/a.e/g));
// [ 'a',
//   index: 3,
//   input: 'How are you doing today?How are you doing today?',
//   groups: undefined ]
// ['are', 'are']

stringObject.replace(regexp/substr,replacement)

用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

参数 作用
regexp/substr 必需。规定子字符串或要替换的模式的 RegExp 对象。请注意,如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象。
replacement 必需。一个字符串值。规定了替换文本或生成替换文本的函数。

返回值: 一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的。

var str = "How are you doing today?"
console.log(str.replace(/ /g, "|"));
console.log(str.replace(/ /g, function () {return '-----'}));
console.log(str.replace(/a.e/g, "were"));

// How|are|you|doing|today?
// How-----are-----you-----doing-----today?
// How were you doing today?

stringObject.split(separator,howmany)

用于把一个字符串分割成字符串数组。

参数 作用
separator 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
howmany 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

返回值: 一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。

            但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。
var str = "How are you doing today?"
console.log(str.split(" ", 3));
console.log(str.split(" "));
console.log(str.split("are"));

// ['How', 'are', 'you']
// ['How', 'are', 'you', 'doing', 'today?']
// ['How', 'you doing today?']

方括号用于查找某个范围内的字符:

var str = 'abcdaabc164984616464646464AAWEGAWGAG';

console.log(str.match(/[a-f]/g).join(''));
console.log(str.match(/[A-F]/g).join(''));
console.log(str.match(/[A-z]/g).join(''));
console.log(str.match(/[0-9]/g).join(''));
console.log(str.match(/[adgk]/g).join(''));
console.log(str.match(/[^a-z]/g).join(''));
console.log(str.match(/(r|b|g)/g).join(''));

// abcdaabc
// AAEAA
// abcdaabcAAWEGAWGAG
// 164984616464646464
// adaa
// 164984616464646464AAWEGAWGAG
// bb

元字符(Metacharacter)是拥有特殊含义的字符:


首先看看通用并且简单的几个元字符.

var str = "Every man in the world! 1, 2, 3, Let's go!!";

console.log(str.match(/m.n/g).join(''));
console.log(str.match(/\w/g).join(''));
console.log(str.match(/\W/g).join(''));
console.log(str.match(/\d/g).join(''));
console.log(str.match(/\D/g).join(''));
console.log(str.match(/\s/g).join(''));
console.log(str.match(/\S/g).join(''));

// man
// Everymanintheworld123Letsgo
//     ! , , , ' !!
// 123
// Every man in the world! , , , Let's go!!
         
// Everymanintheworld!1,2,3,Let'sgo!!

b 和 B 元字符匹配单词边界与否意思是在单词边界匹配的位置,单词字符后面或前面不与另一个单词字符直接相邻。请注意,匹配的单词边界并不包含在匹配中。换句话说,匹配的单词边界的长度为零。(不要与 [b] 混淆。)

var str = "If you love yourself, you can jump into your life from a springboard of self-confidence. If you love yourself, you can say what you want to say, go where you want to go.";

console.log(str.match(/your\b/));//-> 里面的 your
console.log(str.match(/your\B/));//-> 里面的 yourself,yourself

// [ 'your',
//   index: 40,
//   input: 'If you love yourself, you can jump into your life from a springboard of self-confidence. If you love yourself, you can say what you want to say, go where you want to go.',
//   groups: undefined ]
// [ 'your',
//   index: 12,
//   input: 'If you love yourself, you can jump into your life from a springboard of self-confidence. If you love yourself, you can say what you want to say, go where you want to go.',
//   groups: undefined ]

进制数我没用, 不太了解, 下面都是指 W

var str = "If you love yourself, you can jump into your life from a springboard of self-confidence. If you love yourself, you can say what you want to say, go where you want to go.";

console.log(str.match(/\127/gi));// 以八进制数 xxx 规定的字符。console.log(str.match(/\x57/gi));// 以十六进制数 dd 规定的字符
console.log(str.match(/\u0057/gi));// 以十六进制数 xxxx 规定的 Unicode 字符。

其他就字面意思, 不解释了.

量词

var str = "n, On, Oon, Ooon";

console.log(str.match(/o+n/gi));
console.log(str.match(/o*n/gi));
console.log(str.match(/o?n/gi));

console.log(str.match(/o{1}n/gi));
console.log(str.match(/o{1,2}n/gi));
console.log(str.match(/o{3,}n/gi));

console.log(str.match(/^n/gi));
console.log(str.match(/on$/gi));

console.log(str.match(/O(?=on)/));
console.log(str.match(/O(?!on)/));

// ['On', 'Oon', 'Ooon']
// ['n', 'On', 'Oon', 'Ooon']
// ['n', 'On', 'on', 'on']
// ['On', 'on', 'on']
// ['On', 'Oon', 'oon']
// ['Ooon']
// ['n']
// ['on']
// ['O', index: 7, input: 'n, On, Oon, Ooon', groups: undefined]
// ['O', index: 3, input: 'n, On, Oon, Ooon', groups: undefined]

捕获

参数 作用
(n) 匹配 n, 并捕获文本到自动命名的组里
(?:n) 匹配 n 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。

正则表达式一个最重要的特性就是将 匹配成功的模式的某部分进行存储供以后使用 这一能力。对一个正则表达式模式或部分模式两边添加圆括号 () 可以把这部分表达式存储到一个临时缓冲区中。所捕获的每个子匹配都按照在正则表达式模式中从左至右所遇到的内容按顺序存储。存储子匹配的缓冲区编号从 1 开始,连续编号至最大 99 个子表达式。每个缓冲区都可以使用 ’n'(或用 ’$n’)访问,其中 n 为 1 至 99 的阿拉伯数字,用来按顺序标识特定缓冲区(子表达式)。

var str = "I am the best of the best in the best place";
console.log(str.match(/(the best).*\1/g)) //["the best of the best in the best"]

注意的是它只能匹配重复的规则

var str = "aa bb ab";
console.log(str.match(/(\w)\1/g))//["aa", "bb"]
console.log(str.match(/(?:\w)\1/g))//null

意思前面匹配到 a 后面只能也是 a, 哪怕它定制的规则的所有单词
下面是或的简短写法

var str = "better best";

console.log(str.match(/(better|best)/g));
console.log(str.match(/be(?:tter|st)/g));

贪婪与懒惰模式

贪婪模式, 它会尽可能匹配多的字符
懒惰模式, 它会尽可能匹配少的字符
写法区别就是贪婪模式后面加上? 就变成懒惰模式了

var str = "<p>123</p><p>abc</p>";

console.log(str.match(/<p>\S*<\/p>/)); // ["<p>123</p><p>abc</p>", index: 0, input: "<p>123</p><p>abc</p>"]
console.log(str.match(/<p>\S*?<\/p>/)); // ["<p>123</p>", index: 0, input: "<p>123</p><p>abc</p>"]

正则匹配还有很多强大的功能位, 只讲到这里一半是因为水平有限, 平常用到也就这些. 另一半是在 javascript 里支持的功能也是有限, 现阶段讲了也用不到就算了,

————————————————— 实战篇 —————————————————————————–

现在开始来写正题了. 先写个简单的日期匹配练手, 最基础的数字匹配

var str = "2017.04.10 2017-4-10 2017/04/1";

// 基础写法
console.log(str.match(/[0-9]{4}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{1,2}/g));
// 元字符写法
console.log(str.match(/\d{4}.\d{1,2}.\d{1,2}/g));
// 量词写法
console.log(str.match(/\d{4}(.\d+){2}/g));

// ['2017.04.10', '2017-4-10', '2017/04/1']
// ['2017.04.10', '2017-4-10', '2017/04/1']
// ['2017.04.10', '2017-4-10', '2017/04/1']

常见的支付金额校验,纯数字大于 0 最多两个小数

var reg = /^(0|[1-9]\d*)?(\.\d{1,2})?$/g;

console.log('023'.match(reg));
console.log('.5'.match(reg));
console.log('255'.match(reg));
console.log('255.1'.match(reg));
console.log('255.31'.match(reg));
console.log('255.313'.match(reg));

// null
// ['.5']
// ['255']
// ['255.1']
// ['255.31']
// null

这个有点复杂, 折腾很久, 在网上找到好多的答案其实都是错漏百出的, 例如
/^d*.?d{0,2}$/: 它能匹配 012 这种不正常格式
/(^1-9?(.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9].0-9?$)/: 这种不用细看都知道不是合适答案, 也就相当于分拆出每个可能, 发挥不出正则的优势, 我都没往下看了

逐步分析一下我自己写的:
[1-9]d*:开头衹能 1 -9, 后面可不带或带多个数字
^(0|[1-9]d*)?: 或者 0 开头, 也能省略开头
(.d{1,2})?$: 两个小数可要可不要

之前还遇到一个面试题是替换字符串内的变量, 如下

var str = "Hello ${name},you are so ${praise}",
  obj = {
    name: '高圆圆',
    praise: 'goodly'
  };

console.log(str.replace(/\$\{([^{}]+)\}/g, function (match, key) {return obj[key]
}))
// Hello 高圆圆,you are so goodly

————————————————— 尾声 —————————————————————————–
其实应该还有很多东西可以说, 但是目前先熟悉掌握这些就够了, 过段时间我有时间再继续写些进阶知识

退出移动版