一. 正则基础

    在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要,正则表达式就是用于描述这些规则的工具. 换句话说,正则表达式就是记录文本规则的代码. 说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分)能满足正则表达式给出的条件.

正则表达式的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它"匹配"了,否则,该字符串就是不合法的.

元字符 (Metacharacter)

  1. \b

    代表着单词的开头或结尾,也就是单词的分界处. 通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置. 准确的来说,\b匹配这样的位置: 它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w. ( 其中 \w 匹配字母或数字或下划线 [A-Za-z_0-9] ) 即在JS中, /\b\w*\b/g.test("hi.M27") 会返回true.

  1. .

    匹配除了换行符(\n)和回车符(\r)以外的任意字符. [^\n\r]

  1. \s

    匹配空白符,包括空格,制表符(\t),换行符(\n),中文全角空格等. [\t\n\r\f\x0B] (\f换页符)

  1. \S

    匹配非空白符 [^\t\n\r\f\x0B] 如: \S+匹配不包含空白符的字符串.

  1. \w

    匹配字母或数字或下划线 [A-Za-z_0-9]

  1. \W

    匹配非字母、非数字、非下划线 [^A-Za-z_0-9]

  1. \d

    匹配数字 [0-9]

  1. \D

    匹配非数字 [^0-9]

  1. ^

    匹配字符串的开始. /^n\w*/ 匹配以字母n开头的任何字符串

  1. $

    匹配字符串的结束. /\w*n$/ 匹配以字母n结尾的任何字符串

  1. [xyz] (简单类)

    原则上正则的一个字符对应一个字符, 我们可以用[]把它们括起来, 让[]这个整体对应一个字符. 因此上面的正则匹配的就是x|y|z(匹配x、y、z中的任一字符)

  1. [^xyz] (负向类)

    匹配除了xyz这几个字母以外的任意字符. 如: <a[^>]+> 匹配用尖括号括起来的以a开头的字符串.

  1. 字符转义

    如果要匹配元字符本身, 就需要使用转义符号, 如, 在JS中, 匹配.*\ /\.\*\\/.test(".*\") 会返回true.

  1. 分支条件| (管道符)

    正则表达式里的分枝条件指的是有几种规则, 如果满足其中任意一种规则都应该当成匹配, 具体方法是用|把不同的规则分隔开. 如下: 在JS中, /\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}/g.test("(000)-11111111"); 会返回true. /\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}/g.test("000 11111111"); 也会返回true.

量词

    由于元字符与特殊字符或字符类或者它们的组合(中括号)都是一对一进行匹配. 如果我们要匹配"M27", 最简单的都需要/.../, 如果要匹配的字符越长, 正则也就越长. 而量词就是用于处理这种数量关系的.

  1. 简单量词
代码 类型 描述
? 软性量词 出现零次或一次
* 软性量词 出现零次或多次 (任意次)
+ 软性量词 出现一次或多次 (至少一次)
{n} 硬性量词 对应n次
{n, m} 软性量词 至少出现n次但不超过m次
{n, } 软性量词 至少出现n次(+的升级版)
  1. 贪婪匹配与懒惰匹配

    当正则表达式中包含能接受重复的限定符(简单量词)时, 通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符. 以这个正则表达式为例: a.*b, 它将会匹配最长的以a开始, 以b结束的字符串, 如果用它来搜索aabab的话, 它会匹配整个字符串aabab. 这被称为贪婪匹配.
    有时, 我们更需要懒惰匹配, 也就是(在使整个表达式能得到匹配的前提下)匹配尽可能少的字符. 简单量词都可以被转化为懒惰匹配模式, 只要在它后面加上一个问号?. 这样 .*? 就意味着匹配任意数量的重复, 但是在使整个表达式能得到匹配的前提下使用最少的重复.
    如果将a.*?b用于搜索aabab的话, 它会匹配aab(第一到第三个字符)和ab(第四到第五个字符).

// 在JS中, 用正则来移除所有的标签, 只留下innerText:
var html = "<p><a href='http://www.cnblogs.com'>JS</a>by<em>M27</em></p>";
var text = html.replace(/<(.|\s)*?>/g, "-");
console.log(text);   // --JS-by-M27--
1
2
3
4

    如果上面使用的不是懒惰匹配模式的话, 得到的text就是"-", 因为正则默认使用贪婪匹配, /<(.|\s)*>/g将会匹配整个html字符串.

分组

    到目前为止, 我们只能匹配到一个字符, 虽然量词的出现, 能帮助我们处理一排紧密相连的同类型字符. 但这是不够的, 中括号[]表示范围内选择, 大括号{}表示重复次数, 小括号允许我们重复多个字符, 即用小括号来指定子表达式(也叫做分组).
    如下, 是一个匹配IP地址(注意IP地址中每个数字都不能大于255)的正则表达式:
^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$
    关键就是理解(2[0-4]\d|25[0-5]|[01]?\d\d?)这个分组.

console.log( /^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/.exec("192.168.1.0") ); 
// 得到的结果为: ["192.168.1.0", "1.", "1", "0", index: 0, input: "192.168.1.0"]
console.log(RegExp.$1) // 1.
console.log(RegExp.$2) // 1
console.log(RegExp.$3) // 0
1
2
3
4
5
  1. 反向引用 (捕获性分组)

    使用小括号指定一个子表达式后, 匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理. 默认情况下, 每个分组会自动拥有一个组号, 规则是: 从左向右,以分组的左括号为标志,第一个出现的未命名分组的组号为1, 第二个出现的未命名分组的组号为2, 以此类推, 当将未命名分组的组号分配完毕后, 再次的从左向右, 给命名分组分配组号. (因此所有命名组的组号都大于未命名的组号, 并且分组0对应整个正则表达式)
    反向引用用于重复搜索前面某个分组匹配的文本, 每个反向引用都由一个组号或名称来标识, 并通过"\组号"表示法进行引用. 如下:
    →正则表达式\b(\w+)\b\s+\1\b可以用于匹配重复的单词, 像"M27 M27". 这个正则表达式首先匹配到一个单词(\b(\w+)\b), 这个单词会被捕获到编号为1的分组中, 然后是1个或几个空白符(\s+), 最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1).

    →命名分组: 要指定一个子表达式的组名, 可以使用这样的语法: (?<Word>\w+) 这样就把\w+的组名指定为Word了. 要反向引用这个分组捕获的内容 ,可以使用\k<Word>.
    注: 在JS中, 不支持命名的分组.

  1. 非捕获性分组

    当我们不希望某个分组被捕获时(即不想被分配组号), 就可以使用非捕获性分组. 通常在左括号后边加上?:, 也就是(?:pattern)这样就变成了一个非捕获性分组.
    在JS中, 如果不想让该分组被类似match()、exec()这样的函数所获取到, 这个时候就可以将该分组设置为非捕获性分组. 如下:

// (?:exp)不会改变正则表达式的处理方式, 只是这样的组匹配的内容不会被捕获到某个组里面, 也不会拥有组号.
const color = "#990000";
const arr = /#(?:\d+)/.exec(color);
console.log(RegExp.$1);  // ""
console.log(arr);  // ["#990000", index: 0, input: "#990000"]
1
2
3
4
5
  1. 零宽断言 (只用于指定一个满足一定条件的位置)

    前面所有的正则都需要匹配一个或者多个字符, 但是接下来要讲的两个正则它只匹配一个位置, 并不消费任何字符. 接下来的两个正则用于查找在某些内容(但并不包括这些内容)之前的东西, 也就是说它们像\b、 ^、 $那样用于指定一个位置, 这个位置应该满足一定的条件(即断言), 因此它们也被称为零宽断言.

(?=exp)也叫零宽度正预测先行断言(正向前瞻). 匹配exp前面的位置, 它断言此位置的后面能匹配表达式exp. 比如 \b\w+(?=ing\b), 匹配以ing结尾的单词的前面部分(除了ing以外的部分), 如查找 I'm singing while you're dancing. 时, 它会匹配 sing 和 danc.

(?!exp)也叫零宽度负预测先行断言(负向前瞻). 匹配exp前面的位置, 它断言此位置的后面不能匹配表达式exp.

const str1 = "bedroom";
const str2 = "bedding";
const pattern1 = /(bed(?=room))/;
const pattern2 = /(bed(?!room))/; 
console.log(pattern1.test(str1));   // true
console.log(RegExp.$1);             // bed
console.log(RegExp.$2 === "");      // true
console.log(pattern1.test(str2));   // false
console.log(pattern2.test(str2));   // true 
1
2
3
4
5
6
7
8
9

    →注: JS只支持上面的两种前瞻断言.

// 在JS中, 用正则来移除hr以外的所有标签, 只留下innerText: 
const html = "<p><a href='#'>JS</a></p><hr/><p>by<em>M27</em></p>";
const text = html.replace(/<(?!hr)(.|\s)*?>/ig, "-");
console.log(text);   // --JS--<hr/>-by-M27—

// 在JS中, 假如你想要给一个很长的数字中每三位间加一个逗号, 可以如下操作:
const html = "123456780123456789";
const reg = /(\d{3})(?=\d)/g;
console.log(html.replace(reg, str => str + ","));
// console.log(html.replace(reg, "$1,"));

// 在JS中, 假如需要匹配密码必须有至少一个大写字母, 一个小写字母, 一个数字, 并且长度至少6位而且只能是数字字母组合, 可以如下操作:
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)[A-Za-z0-9]{6,}$/.test("123Abc");  // true
1
2
3
4
5
6
7
8
9
10
11
12
13

二: JS正则

    ECMAScript通过RegExp类型来支持正则表达式. 使用类似Perl的语法, 就可以创建一个正则表达式: var pattern = /pattern/flags;
    其中的模式(pattern)部分可以是任何简单或者复杂的正则表达式, 可以包含字符类、量词、分组、正向查找(前向断言)和反向引用. 每个正则表达式都可以带有一或多个标志(flags),用以表明正则表达式的行为.

正则表达式的匹配模式支持以下3个标志:

  1. g: 全局(global)模式.
  2. i: 表示不区分大小写(case-insensitive)模式, 即在确定匹配项时忽略模式与字符串的大小写.
  3. m: 表示多行(multiline)模式. 即在到达一行文本末尾时, 还会继续查找下一行中是否存在于模式匹配的项.

    与其他语言的正则类似, 模式中使用的所有元字符都必须转义, 其中包含的元字符如下: ( [ { \ ^ $ | ? * + . } ] )
    既可以使用字面量形式创建正则表达式, 也可以使用 RegExp 构造函数创建正则表达式, 它接收两个字符串参数: 一个是要匹配的字符串模式, 另一个是可选的标志字符串, 如下:

const pattern = new RegExp("\\[bc\\]at", "ig");
1

    需要注意的是, 传递给RegExp构造函数的两个参数都是字符串(不能把正则表达式字面量传递给RegExp构造函数), 由于其模式参数是字符串, 因此在某些情况下要对字符进行双重转义, 所有的元字符都必须双重转义, 那些已经转义过的字符也是如此, 如下:

正则字面量形式 等价的RegExp字符串参数
/\[bc\]at/ "\\[bc\\]at"
/\.at/ "\\.at"
/name\/age/ "name\\/age"
/\w\\hello\\123/ "\\w\\\\hello\\\\123"

RegExp实例属性

RegExp的每个实例都有下面的属性, 通过这些属性就可以获得有关模式的各种信息.

  1. global: 布尔值, 是否设置了g标志.
  2. ignoreCase: 布尔值, 是否设置了i标志.
  3. multiline: 布尔值, 是否设置了m标志.
  4. lastIndex: 整数, 表示开始搜索下一个匹配项的字符位置, 从0算起.
  5. source: 正则表达式的字符串表示, 按照字面量的形式而非传入构造函数中的字符串模式返回.

通过上面的这些属性可以获知一个正则表达式的各方面的信息,但是却没有太大的用处,因为这些信息全都包含在模式声明中了.

RegExp实例方法

1. exec()方法.

RegExp对象的主要方法是exec(), 该方法是专门为捕获组而设计的. exec()方法接受一个参数, 即要应用模式的字符串, 然后返回包含第一个匹配项信息的数组, 或者在没有匹配项的情况下返回null.

    注: 在返回的数组中, 第一项是与整个模式匹配的字符串, 其他项是与模式中的捕获组匹配的字符串( 如果模式中没有捕获组,则该数组只包含一项 ), 返回的数组中还包含两个额外的属性: index和input. 其中, index表示匹配项在字符串中的位置, input表示应用正则表达式的字符串. 如下:

const str = "cat, bat, sat, fat";
const pattern = /(.)at/;
let matchs = pattern.exec(str);
console.log(matchs);    // ["cat", "c", index: 0, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "cat"   第一个匹配项
console.log(matchs[1]); // "c"     捕获组匹配的字符串
console.log(pattern.lastIndex);   // 0 表示开始搜索下一个匹配项的字符位置

matchs = pattern.exec(str);
console.log(matchs);    // ["cat", "c", index: 0, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "cat"   第一个匹配项
console.log(matchs[1]); // "c"     捕获组匹配的字符串
console.log(pattern.lastIndex);   // 0 表示开始搜索下一个匹配项的字符位置
/*
** 在不设置全局标志(g)的情况下, 在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息.
** 而在设置了全局标志(g)的情况下, 每次调用 exec() 则都会在字符串中继续查找新匹配项. 如下:
*/
const str = "cat, bat, sat, fat";
const pattern = /(.)at/g;
let matchs = pattern.exec(str);
console.log(matchs);    // ["cat", "c", index: 0, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "cat"   第一个匹配项
console.log(matchs[1]); // "c"     捕获组匹配的字符串
console.log(pattern.lastIndex);   // 3 表示开始搜索下一个匹配项的字符位置     

matchs = pattern.exec(str);
console.log(matchs);    // ["bat", "b", index: 5, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "bat"   第一个新的匹配项
console.log(matchs[1]); // "b"     捕获组匹配的字符串
console.log(pattern.lastIndex);   // 8 表示开始搜索下一个匹配项的字符位置
/*
** 因为模式 pattern 是全局模式, 因此每次调用 exec() 都会返回字符串中的下一个匹配项, 直到搜索到字符串末尾为止.
** 注意模式的 lastIndex 属性的变化, 在全局模式匹配下, lastIndex的值在每次调用exec()后都会增加, 而在非全局模式下则始终保持不变. 
** 对于exec()方法, 即使在模式匹配中设置了全局标志(g), 它每次也只会返回一个匹配项!
** -----------------------------------------------
** lastIndex 表示匹配成功时候, 匹配内容最后一个字符所在原字符串中的位置+1.
** 也就是匹配内容的下一个字符的index(如果匹配内容在字符串的结尾, 同样返回原字符串中的位置+1, 也就是字符串的length, 如果匹配失败则重置 lastIndex 的值为0).
** 如果未带参数g, lastIndex始终为0.
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

2. test()方法.

该函数接收一个字符串参数, 在模式与该参数匹配的情况下返回true, 否则,返回false. 在只想知道目标字符串与某个模式是否匹配, 但不需要知道其文本内容的情况下, 使用这个方法是非常方便的.
如果调用 test 方法的正则表达式中设置了全局标志(g), 那么在每次匹配的时候, 该正则表达式的 lastIndex 属性的值同样是会变化的.

3. toString()方法.

RegExp实例的 toString() 方法会返回正则表达式的字面量字符串, 与创建正则表达式的方式无关

4. valueOf()方法.

RegExp实例的 valueOf() 方法则会返回正则表达式本身

与正则相关的字符串方法

1. match()方法.

在字符串上调用这个方法, match()方法只接受一个参数, 要么是一个正则表达式, 要么是一个RegExp对象.
match()方法可在字符串内检索指定的值, 或找到一个或多个正则表达式的匹配, match()方法返回一个数组, 该数组的内容依赖于正则表达式是否具有全局标志g.

  1. 当正则表达式不带有全局标志g时.

    如果 regexp 没有标志g, 那么 match() 方法就只能在字符串中执行一次匹配.
    如果没有找到任何匹配的文本, match()将返回 null. 否则, 它将返回一个数组, 在数组中, 第一项是与整个模式匹配的字符串, 其他项是与模式中的捕获组匹配的字符串 (如果模式中没有捕获组, 则该数组只包含一项).
    返回的数组中还包含两个额外的属性: index 和 input. 其中, index 表示匹配项在字符串中的位置, input 表示应用正则表达式的字符串. (同正则表达式exec()方法的返回值)

  1. 当正则表达式带有全局标志g时.

    如果 regexp 具有标志g, 则 match() 方法将执行全局检索, 找到字符串中的所有匹配子字符串.
    若没有找到任何匹配的子串, 则返回null, 如果找到了一个或多个匹配子串, 则返回一个包含所有的匹配子串的数组, 且数组中不再包含 index 属性或 input 属性.

// 注: match()不管是否带g(全局匹配), 连续多次匹配获取的结果都一样. 如下:
const str = "cat, bat, sat, fat";
const patternOne = /(.)at/;
let matchs = str.match(patternOne);
console.log(matchs);    // ["cat", "c", index: 0, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "cat"   第一个匹配项
console.log(matchs[1]); // "c"     捕获组匹配的字符串

matchs = str.match(patternOne);
console.log(matchs);    // ["cat", "c", index: 0, input: "cat, bat, sat, fat"]
console.log(matchs[0]); // "cat"   第一个匹配项
console.log(matchs[1]); // "c"     捕获组匹配的字符串
--------------------------------------------------------------------
const patternTwo = /(.)at/g;
matchs = str.match(patternTwo);
console.log(matchs);    // ["cat", "bat", "sat", "fat"]
console.log(matchs[0]); // "cat"
console.log(matchs[1]); // "bat"

matchs = str.match(patternTwo);
console.log(matchs);    // ["cat", "bat", "sat", "fat"]
console.log(matchs[0]); // "cat"
console.log(matchs[1]); // "bat"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2. seach()方法.

search() 方法用于检索字符串中指定的子字符串, 或检索与正则表达式相匹配的子字符串.(如果要执行忽略大小写的检索,请在正则表达式后面追加标志i, 且该方法不执行全局匹配, 它将忽略标志g. 😃)
search()方法返回字符串中第一个匹配项的索引, 如果没有匹配项, 则返回-1. (注: search方法始终是从字符串开头向后查找匹配的.)

const str = "M27 is stupid!";
const pos = str.search(/M27/i);  // 0
1
2

3. replace()方法.

replace() 方法简化了替换子字符串的操作, 该方法接受两个参数, 返回一个用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的新的字符串. 如下:
string.replace(regexp/substr, replacement);

  1. regexp/substr

    必需, 一个正则表达式或是一个字符串(该字符串不会被转换成正则表达式). 如果是字符串, 则只会替换第一个子字符串, 想要替换所有的子字符串, 唯一的办法就是提供一个指定全局标志(g)的正则表达式.

  1. replacement

    必需, 一个字符串值. 规定了替换文本或生成替换文本的函数.
  注: 当 replacement 是字符串时, 那么每个匹配都将由字符串替换. 但是 replacement中的 $ 字符具有特定的含义. 如下表所示, 它说明从模式匹配得到的字符串将用于替换.

字符 替换文本
$1、$2、...、$99 匹配 regexp 中的第1到第99个捕获组的子字符串
$& 匹配整个模式的子字符串
$` 位于匹配子串左侧的文本. 与 RegExp.leftContext 的值相同
$' 位于匹配子串右侧的文本. 与 RegExp.rightContext 的值相同
$$ 直接量符号$

    当 replacement 为函数时, 在这种情况下, 每个匹配都调用该函数, 它返回的字符串将作为替换文本使用. 该函数的第一个参数是与模式匹配的字符串, 接下来的参数是与模式中的捕获组匹配的字符串( 如果有的话 ), 可以有0个或多个这样的参数, 接下来的参数是一个整数, 声明了匹配在 string 中出现的位置, 最后一个参数是 string 本身. 如下, 实现下划线命名转驼峰命名:

function changeVarStyle(str) {
   return str.replace(/\_([a-zA-Z0-9])+/g, match => match.charAt(1).toUpperCase() + match.slice(2).toLowerCase());
}
1
2
3

4. split()方法.

该方法可以基于指定的分隔符分割将一个字符串分割多个子字符串, 并将结果放在一个数组中返回.
分隔符可以是字符串, 也可以是一个 RegExp 对象. string.split(separator, howmany)
如果把空字符串('')用作 separator, 那么 string 中的每个字符之间都会被分割.

RegExp 构造函数属性

    RegExp构造函数包含一些属性. 这些属性使用于作用域中的所有正则表达式, 并且基于所执行的最近一次正则表达式操作而变化. 这些属性分别有一个长属性名和短属性名(Opera是例外, 它不支持短属性名). 下面列出了RegExp构造函数的属性:

字符 替换文本
input ( $_ ) 最近一次要匹配的字符串. Opera未实现此属性
lastMatch ( $& ) 最近一次的匹配项. Opera未实现此属性
lastParen ( $+ ) 最近一次的匹配的捕获组. Opera未实现此属性
leftContext ( $` ) 位于匹配子串左侧的文本.
rightContext ( $' ) 位于匹配子串右侧的文本.

    使用这些属性可以从 exec() 和 test() 执行的结果中提取出更具体的信息, 如下:

const text = "this is a short summer";
const pattern = /(.)hort/g;
if (pattern.test(text)) {
  console.log(RegExp.input);         // this is a short summer
  console.log(RegExp.lastMatch);     // short
  console.log(RegExp.lastParen);     // s
  console.log(RegExp.leftContext);   // this is a 
  console.log(RegExp.rightContext);  // summer
}
/*
** 上面的长属性名都可以使用相应的短属性名来访问.
** 由于这些短属性名大多都不是有效的 ECMAScript 标识符, 因此必须通过方括号语法来访问它们, 即:
   RegExp.lastMatch         →  RegExp["$&"]
   RegExp.lastParen         →  RegExp["$+"]
   RegExp.leftContext       →  RegExp["$`"]
   RegExp.rightContext      →  RegExp["$'"]
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  注: 除了上面介绍的几个属性, 还有9个用于存储捕获组的构造函数属性. 访问这些属性的语法是 RegExp.$1, RegExp.$2, RegExp.$3 .... RegExp.$9. 分别用于存储第一、第二、第三 .... 第九个匹配的捕获组. 在调用exec()或者test()方法时, 这些属性会被自动填充.

三: JS正则使用例子

// 1. 获取 url 中的查询参数.
function getParam(name) {
  const reg = new RegExp(`\\b${name}=([^&#?]*)`);
  const m = window.location.href.match(reg);
  return m ? m[1] || '' : '';
}

// 2. 获取 url 中的 Hash 值.
function getHashString() {
  const s = window.location.href;
  const m = s.match(/#([^&#?]*)/);
  return m ? m[1] || '' : '';
}

// 3. 设置 url 中的查询参数.
function setUrlParam(link, name, value) {
  let currentUrl = link.split('#')[0];
  const reg = new RegExp(`${name}=([^&]*)`);
  if (/\?/g.test(currentUrl)) {
    if (reg.test(currentUrl)) {
      currentUrl = currentUrl.replace(reg, `${name}=${value}`);
    } else {
      currentUrl += `&${name}=${value}`;
    }
  } else {
    currentUrl += `?${name}=${value}`;
  }
  if (link.split('#')[1]) {
    currentUrl += `#${link.split('#')[1]}`;
  }
  return currentUrl;
}
function setUrlParams(link, obj){
  for (let i in obj) {
    link = setUrlParam(link, i, obj[i]);
  }
  return link;
}

// 4. 驼峰命名转下划线/连接符命名 ( componentMapModelRegistry )
function changeVarStyleOne(str) {
  return str.match(/^([a-z][a-z0-9]+)|([A-Z][a-z0-9]*)/g).join('_').toLowerCase();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Last Updated: 1/14/2019, 8:59:28 PM