2020年的文档是可以看懂的, 但是不够

新的匹配

\p{P}表示中英文标点
\p{Han}表示所有中文字符
/xxx/u # 这里的u表示按照utf-8匹配

12圣斗士

需要\转义的12圣斗士: [ ] \ ^ $ . | ? * + ( )
1. [这是字符集, 选择这里面的所有的单个字符]
2. \本身就是转义符, 因此得用\\
3. ^代表一行的开始, 在[代表除...以外]
4. $匹配行尾 多行匹配:换行符之前的位置, 单行匹配输入结束位置.
5. .任意asc码, 包含, 英文, 数字, 和几个简单符号, 不包含任意的换行符.
6. |或, a|b, 就是a或者b
7. ?*+这是西方三圣, 下面有解释.
8. (捕获符号) 每个被匹配的正则可以用\1, \2这样的项目在匹配环节指代, 到了替换环节要用$1, $2, 如此神奇的语法还是因为perl.
9. {}这一套也是需要转义的

括号都有用(){}[]

括号
1. 圆括号“()”才能用于形成引用匹配组。
2. “[]”用于定义字符集。
3. “{}”用于定义重复操作。

字符集[中括号]

需要转义的4大金刚: ] \ ^ -
1. ]负责封口
2. \转3. ^非, 不要里面的内容. [^x] 除了x之外的所有符号
4. -代表范围, [a-z], 匹配26个小写字母.[A-Za-z0-9]匹配所有英文字母和数字.

量词{大括号}

西方三圣: ?+*  
- , 是关键字符
- 2到4次: {2,4} #注意这里是逗号, 不是减号
- ? 0次或1次.{0,1}
- + 1次或多次.{1,} 在量词描述中, 如果是空, 则代表无限制.
- * 任意次. {,} 此种写法不可以, 第一个项目不能省略. {0,}这样就是对的了.
- 刚好n次{n} 
- 一般以上的量词匹配都是贪婪的任意量词的后面的?, 会导致量词变为懒惰的: *?

小括号比较复杂

引用匹配(小括号)

后面要讲的问号叹号就是小括号的一部分.

1. (xxx)代表一个匹配项目, 
2. \1, \2这种数字, 在匹配中可以用作逐个引用.
3. 替换部分要用$1, $2, 这样的, 别看我, 都是perl惹的祸. 
举例: 
var tt='barfoo把foobarfoobar'.replace(  /(foo)(bar)\1\2/, '$2 $1' );
此时 tt='barfoo把bar foo';


var re = /(.)*th/;
var str = "John Smith";
var newstr = str.replace(re, "$1");
console.log(newstr); //这里输出的是i, 因为th被尾巴匹配吃掉了, 所以最后一个字母i就出现了.
//情况如此之诡异, (任意内容){任意数量} 这种情况很难被记住被理解以及被正确使用.
//(某内容){量词}, 这个时候()就是在量词范围内的最后一次匹配
//这里的理由就是, group如果被多次匹配, 那么它永远保留最后一次匹配.
小括号和或
如果使用| 那么必须用小括号括起来他的作用域, 否则他的作用域就一直延展开来, 除非碰到另一个|或符号.
因此使用或|的正常格式是:
(exp1|exp2|exp3|.....)

一大堆转义

太多了, 我们挑有用的说说.

\0 代表U+0000, null字符.
\0555, 代表8进制数字555.
\x56, 代表16进制数字56.
\u3838, 代表U3838, 4位的16进制字符3838.
\1, 代表重复的第一个()的匹配内容.

定位符号

1. 行开始和结束: ^$, 纯定位符, 同样不占位, 不消耗任何字符.
2. \b词的边界, /\bm/匹配“moon”中得‘m’;注意: JavaScript的正则表达式引擎将特定的字符集定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,小数位数和下划线字符。不幸的是,重要的字符,例如“é”或“ü”,被视为断词。 边界不占位, 纯的定位符.
3. \f 匹配换页符. 一个真实的字符. 占位的.
4. \n 换行 0A  一个真实的字符. 占位的.
5. \r 换行 0D 一个真实的字符. 占位的.
7. \t 水平制表符,  一个真实的字符. 占位的.
8. \v 垂直制表符, 这货就是ctrl+k, 或者word里面ctrl+enter, 滚屏用的符号, 一般文本没有这个.

空白字符

[\s\S]
- \s 空白字符 含空格 tab(制表符) 换页 换行,  一个真实的字符. 占位的.
- \S 代表除了空白字符之外的字符
- 因此, 任意字符就是[\s\S], 这个是一个很不错的范式. 不过这个范式一般的使用要切换为懒惰模式
   [\s\S]*?
- 否则这个模式[\s\S]*会一直吃到全字符串结束, 关于贪婪和懒惰, 后文有介绍.

\s之坑
- 然后, \s还是有坑的, 如果拿他当\n用, 他会顺便吃掉下一行的所有缩进字符. 
- 因此使用\s要慎重, 尽量指明是空格, tab, 还是回车, 尽量避免使用\s是避免采坑的法则.

要同时匹配tab和空格咋办?
- 虽然我们可以用|方式解决比如: [ |	]
- 但是最好的方式是, 先做一个预处理, 把所有的tab都替换为2个空格, 然后再统一处理空格.

匹配模式

const reg=/xxx/ooo; // 这里的ooo就是匹配模式
1. g 全局模式, 能匹配多少就匹配多少, 否则匹配到第一个符合的就停止了.
2. i 忽略大小写模式
3. m 多行模式, m标志用于指定多行输入字符串应该被视为多个行如果使用m标志^和$匹配的开始或结束输入字符串中的每一行而不是整个字符串的开始或结束
4. y 继续模式, 不从头开始, 从当前位置开始匹配
例如: /xxx/gimy 也就这样了, 一共就这么四个模式标志

常用函数

  • regexObj.exec(str) 可以步进式搜索
  • regexObj.test(str) 判断是否匹配
  • string.search(regexp), 匹配, 并返回匹配位置
  • string.match(regexp), 匹配, 但是充满模式
  • string.matchAll(regexp), 匹配所有的结果
  • string.replace(regexp substr, newSubstr function) 替换 const newStr = str.replace(regexp substr, newSubstr function)
  • string.split([separator[, limit]]), 切割一个字符串

以js的名义

//设置一个表达式
const regex = /ab+c/gim;

/*
1. regexp.exec()	 匹配RegExp,返回一个数组, 这个数组描述的是一次匹配, 之所以是数组是为了小括号, 所以为了得到所有匹配, 需要反复运行exec, 直到未匹配到则返回null.
2. string.search是为了判断模式是否存在.返回模式所处位置. 类似的还有regexp.test()返回true/false.
3. string.match的表现很分裂, 
  	如果是g模式可以一次把所有完整匹配都配出来, 但是没有()匹配组什么事了.
		如果不是g模式, 那么就是第一个匹配, 并且有()匹配组. 简单地说表现和reg.exec一致.
4. string.matchAll()这个很好, 返回一个iterator, 每个元素都包含所有的匹配组.*/
//样例代码
let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';
let array = [...str.matchAll(regexp)];//三种方式处理:  for...of, array spread, Array.from() :
array[0]//  Array ["test1", "e", "st1", "1"]
array[1];//  Array ["test2", "e", "st2", "2"]


const newStr = str.replace(regexp|substr, newSubstr|function)	 //使用字符串(第二个参数)替换掉(RegExp-第一个参数, 匹配到的子字符串)。

var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");
var tt='barfoo把foobarfoobar'.replace(  /(foo)(bar)\1\2/, '$2 $1' );

//处理标题, 这个也是段落的区分, 既然段落只是由于h区分, 那么段落也加在这里好了.
  var rex=/^(\#{1,6})( )([^\n]*)\n/gmi;	
  //正则表达式, 行头标志, 1-6个#必须顶着行写,  然后是任意非换行字符, 然后是一个换行字符. 
  var h_result=t.replace(rex,		 
    function(match, p1, p2, p3) {
    var ln=p1.length;
    return '<h'+ln+'>'+p3+'</h'+ln+'>\n<p>'  //很酷啊, 直接给个p段落就ok了. 
  });

问号

问号可以单独作为一节

#量词 代表0次或者1次

# 零宽断言, js里面都是推的
(?:x) 匹配但是不记录在\1, \2, 或者$1, $2这种匹配项目里面, 记得用小括号包住他们,否则会悲剧. //这个其实没卵用, 用了就知道了, 根本不起作用, 这货的意思并不是不记录在匹配结果, 而只是忽略这个括号, 也就是说这个小括号并不单独形成一个匹配, 总之js没有前置零宽断言, 只有下面两种后置零宽断言
x(?=y) 匹配后面有y的x
x(?!y)匹配后面没有y的x

(?!x)b这个项目匹配b前面没有x的情况, 这么用是可以的, 但是, js不支持前推零宽断言, 也就是说, 这个不是x的东西也会被匹配到结果里面.
t=t.replace(/((?!^)>)|(>(?! ))/gm,'&gt;')
如果js不支持前推零宽断言, 那么这里有bug, ((?!^)>) 这个会吃掉>左边的一个字符
但是真的很神奇, 实际上竟然是不吃掉左边第一个字符的, 
也就是说, js的replace实际是支持前推0宽断言的.

# 懒惰
任意量词的后面的?, 会导致量词变为懒惰的: *?

问号 - 贪婪和懒惰, 永远也躲不开的问题.

  1. 用字符集反相匹配解决懒惰匹配问题, 用贪婪反向排除匹配更好 [^ ooo ]
  2. *?其实任意量词后面加问号就是懒惰了.

问号 - 零宽断言(js的这个就是个杯具)

/?!逆向断言/ #这里断言的是后面没有. 都是后推零宽断言, js不支持前置零宽断言.
#比如不是行首
/?!^/   #rex=/((?!^)>)|(>(?! ))/gmi; //负向零宽断言 
#前瞻断言 
#正向前瞻用来检查接下来的出现的是不是某个特定的字符集。
#而负向前瞻则是检查接下来的不应该出现的特定字符串集。
#零宽断言是不会被捕获的。
(?=exp)	正向前瞻	匹配exp前面的位置
(?!exp)	负向前瞻	匹配后面不是exp的位置
(?<=exp)	正向后瞻	匹配exp后面的位置, js不支持
(?<!exp)	负向后瞻	匹配前面不是exp的位置, js不支持
所以, (?!exp)	这个全称是: 负向前瞻零宽断言, 因为js没有后瞻, 所以简称负向零宽断言
#后瞻断言
#后瞻断言也叫零宽度正回顾后发断言 (?<=表达式) 表示匹配表达式后面的位置
#例如(?<=abc).* 可以匹配abcdefg中的defg

*? #任意量词后面的?会导致前面的量词懒惰. 
var rex=/([>] )([\s\S]*?)\n\n/gmi;	#此处有两个回车结束block, 单个回车可以忽略
//这个就是>开头的任意的字符之后两个回车结尾.

正则和字符串互相转化

var regex = /abc/g;
var str = regex.source; // "abc"
var restoreRegex = new RegExp(str, "g");

var regex = /abc/g;
var str = regex.toString(); // "/abc/g"
var parts = /\/(.*)\/(.*)/.exec(str);
var restoredRegex = new RegExp(parts[1], parts[2]);

var regex = /abc/g;
var str = regex.toString(); // "/abc/g"
var lastSlash = str.lastIndexOf("/");
var restoredRegex = new RegExp(str.slice(1, lastSlash), str.slice(lastSlash + 1));

要点

  1. 不要动态new一个正则出来, 转义能搞死人.
  2. 碰到有正则的动态需求, 可以考虑正则和字符串互相转化的办法

核心内容回顾

括号 量词 作用域
{} * g
[] + m
() ? i