去年的文档写的一点都不清晰, 导致我今年要重写一遍.

12圣斗士

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

字符集[单聊中括号]

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

量词{单聊大括号}

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

括号

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

引用匹配(小括号最复杂)

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

1. (xxx)代表一个匹配项目, 
2. \1, \2这种数字, 在匹配中可以用作逐个引用.
3. 替换部分要用$1, $2, 这样的, 别看我, 都是perl惹的祸. 
举例: 
var tt='barfoo把foobarfoobar'.replace(  /(foo)(bar)\1\2/, '$2 $1' );
此时 tt='barfoo把bar foo';
//此处要重点解释几个事情, 这个非常重要.
//
////////////////////////////////
//  (any)* 这种形式, $n代表的是最后一个匹配到的项目.

console.log('121342344124'.replace(t, '$1')) //这里输出的是4.	

var re = /(.)*th/;
var str = "John Smith";
var newstr = str.replace(re, "$1");
console.log(newstr); //这里输出的是i, 因为th被尾巴匹配吃掉了, 所以最后一个字母i就出现了.
//情况如此之诡异, (任意内容){任意数量} 这种情况很难被记住被理解以及被正确使用.
//因此, 最终的建议是: (小括号)之后如果写了量词, 那么不要用这个括号作为匹配项目, 不要使用$n形式去引用这个小括号.

小括号和或

如果使用| 那么必须用小括号括起来他的作用域, 否则他的作用域就一直延展开来, 除非碰到另一个|或符号.
因此使用或|的正常格式是:
(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个空格, 然后再统一处理空格.

匹配模式

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

以js的名义

//设置一个表达式
const regex = /ab+c/gim;
一共有6个函数, 一般exec+replace就够用了.
regexp.exec()	 匹配RegExp,返回一个数组(未匹配到则返回null
string.replace()	 使用字符串(第二个参数)替换掉(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 rex=/(^[>] )([\s\S]*?)\n\n/gmi;	//[\s\S]所有字符

|(或)和$1之间的关系. 

SyntaxError: Invalid regular expression: nothing to repeat

这是一个很常见的bug. 例如: 
var regex = new RegExp("^ {"+p1.length+"}\* ([^\n]*)", "gm")
就会遇到这个bug:
SyntaxError: Invalid regular expression: nothing to repeat
查阅Stack Overflow, 发现是特殊字符转义引起的, 去除上面的\*就没有任何问题了. 咋办呢?
var regex = new RegExp("^ {"+p1.length+"}[*] ([^\n]*)", "gm")		//这句就ok了.
但是, 我不解的是, 为啥转义符失效了?
var regex = new RegExp("^ {"+p1.length+"}\\* ([^\n]*)", "gm")		//这样也可以了, 奶奶的竟然要我双重转义.

结论: 不到万不得已, 不要动态构造正则, 尽量别new regexp, 一堆坑. 很多时候这货能正常运行其实只是一个巧合. 
还可以得出一个结论, 中括号[里面的转义都是正确的, 真神奇]:         
var rex=/^( *)([0-9]+\.|[*+\-]) [^\n]*\n(^\1([0-9]+\.|[*+\-])? [^\n]*\n)*/gm		

核心内容

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

剩下的就是些特殊符号了: 12圣斗士, 本文开头有介绍