正則
小編自己也在學習正則,分享給大家一起學習,本篇文章適用于對正則還不是了解的初級選手,廢(xia)話(che)不(dan)多說,直接進入正題。
正則表達式定義
我們知道在js中正則表達式用RegExp
對象表示,可以使用RegExp()
構造函數來創建RegExp
對象,但是在更多的情況下我們是用直接量的語法來創建。
栗子:
//使用直接量的形式
var reg = /\w$/;
//使用構造函數的形式
var reg = new RegExp('\w$');
但我們如果有動態生成一個正則表達式的話,就需要我們用構造函數的形式了,我們又舉個栗子:
var fmt = 'AA-bb cc:dd:ee';
var date = new Date();
var fmtObj = {
'A+': data.getMonth() + 1,
'b+': date.getDate(),
'c+': dategetHours(),
'd+': date.getMinutes(),
'e+': date.getSeconds(),
}
for(let key in fmtObj) {
if(new RegExp(`(${key})`).test(fmt)){
fmt = fmt.repalce(RegExp.$1, RegExp.$1.lengt === 1 ? fmtObj[key] : ('00' + fmtObj[key]).substr((fmtObj[key] + '').length))
}
}
那么什么是直接量字符呢?
直接量字符
正則表達式中所有的字母和數字都是按照字面量的含義進行匹配的,js的正則表達式也支持非字母的字符匹配,這些字符需要通過反斜杠\
進行轉譯。下列表格中列舉一些
名稱 | 內容 |
---|---|
\o | NUL字符(\u0000) |
\t | 制表符(\u0009) |
\n | 換行符 (\u000A) |
\v | 垂直制表符(\u000B) |
\f | 換頁符(\u000C) |
\r | 回車符(\u000D) |
\xnn | 由16進制數nn指定的拉丁字符,例如,\x0A等價于\n |
\uxxxx | 由16進制數xxxx指定的unicode字符,例如\u0009等價于\t |
\cX | 控制字符^X,\cJ等價于換行符\n |
字母和數字字符 | 匹配自身 |
特殊含義的符號和字符
在正則表達式中,許多標點符號具有特殊的含義,他么是:^ $ . * + ? = ! : | / \ ( ) { } [ ];
某些符號只能在正則表達式的某些上下文中才會具有某種的特殊意義,在其他的上下文當中則會被直接當成直接量處理。如果想要在正則表達式中使用這些符號的直接量進行匹配,則必須使用前綴\
, 這是一條通用的規則,其他標點符號(例如@和引號)沒有特殊含義,在正則表達式中按照字面量含義進行匹配,如果不記得哪些標點符號需要反斜杠轉義,可以在每個標點符號前面都加上轉義符\
。另外需要注意,許多字母和數字在有反斜杠做前綴時會有特殊含義,所以對于想按照直接量進行匹配的數字和字母,盡量不要用\
進行轉義
字符類
將直接量字符單獨放進中括號內就組成了字符類。一個字符類可以匹配它所包含的任意字符,因此表達式/[abc]/
就和字母abc中的任意一個都匹配
var reg = /[a-z0-9]/;
上面的正則表達式可以匹配任意一個字母或數字,其中a-z
表示a-z的所有字母,0-9
表示0到9之間的數字
反字符類[^...]
就是匹配字符類中不含有的字符,用^符號開頭
var reg = /[^a-z0-9]/;
上面的正則表達式的意思就是匹配任意一個不含有字母和數字的字符
正則表達式的字符類
符號 | 內容 |
---|---|
[...] | 字符類 |
[^...] | 反字符類 |
. | 除換行符和其他unicode行終止符之外的任意字符 |
\w | 數字,字母和下劃線, 等價于[a-z0-9_] |
\W | 非數字,字母和下劃線, 等價于[^a-z0-9_] |
\s | 任何unicode 空白符 |
\S | 非任何unicode 空白符 |
\d | 數字 |
\D | 非數字 |
\b | 單詞邊界 |
\B | 非單詞邊界 |
正則表達式的重復語法
符號 | 內容 |
---|---|
{n, m} | 最少匹配n次,最多匹配m次 |
{n, } | 至少匹配n次或更多 |
{n} | 匹配n次 |
? | 匹配0次或一次 |
+ | 最少匹配一次 |
* | 匹配0次或多次 |
非貪婪模式
上面我們列舉出了匹配重復字符是盡可能多的匹配,而且允許后續的正則表達式繼續匹配。因此我們稱之為'貪婪'匹配模式。我們同樣可以使用的正則表達式進行非貪婪匹配。只需要在待匹配的字符后跟隨一個問號即可:'??', '+?', '*?' 或者'{1, 5}?'
var a = /[a-z0-9]+?/
a.exec('sdfff900') // ["s", index: 0, input: "sdfff900", groups: undefined]
// 使用非貪婪模式匹配得到的結果可能和期望并不一樣,可以來看看看下面的這個例子:
var b = /a+?b/;
b.exec('aaab') // ["aaab", index: 0, input: "aaab", groups: undefined]
var c = /shen(ab)+?/;
b.exec('shenababab'); // ["shenab", "ab", index: 0, input: "shenababab", groups: undefined]
這是因為正則表達式的模式匹配總是會尋找字符串中第一個可能匹配的位置,由于這個匹配是從字符串的第一個字符開始的,因此在這里不考慮他的子串中更短的匹配
選擇,分組和引用
正則表達式語法還包括指定選擇項,子表達分組和引用前一子表達式的特殊字符,我們分別來看一下
選擇項: |
用于分隔供選擇的字符。
/ab|cd|ef/
,可以匹配串ab,也可以匹配cd或者是ef。
注意選擇項的嘗試匹配的次序是從左到右,直到發現了匹配的項。如果左邊的選擇項匹配,就忽略右邊的選擇項,即使產生更好的匹配。所以,當正則表達式/a|ab/
匹配字符串ab
時,他只能匹配第一個字符串a
子表達式:
正則表達式中的圓括號有多種作用。
把單獨的項組合成子表達式,以便可以像處理單獨的單元那樣用 | * + ? 等來對單元內的項進行處理。
var a = /java(script)?/; // 可以匹配字符串 'java',其后可以有'script'也可以沒有。
var b = /(ab|cd)+|ef/; // 可以匹配字符串ef, 也可以匹配ab或者cd的一次或多次重復
在完整的模式中定義子模式,當一個正則表達式和目標字符串成功匹配時,可以從目標字符串中拿到和圓括號中的字符模式相匹配的部分。
var a = /[a-z]+(\d+)/; // 那么這個時候我們可以從檢索到的匹配中拿到和圓括號中子模式相匹配的數字
()
表示捕獲分組,()
會把每個分組里的匹配的值保存起來,使用$n
(n是一個數字,表示第n個捕獲組的內容)
var reg = /\b(shen(sxx))/;
reg.test('shensxx');
console.log(RegExp.$1, RegExp.$2) // shensxx sxx
reg.exec('shensxx');
// ["shensxx", "shensxx", "sxx", index: 0, input: "shensxx", groups: undefined]
(?:)
表示非捕獲分組,和捕獲分組唯一的區別在于,非捕獲分組匹配的值不會保存起來。
var reg = /\b(shen(?:sxx))/;
reg.test('shensxx');
console.log(RegExp.$1, RegExp.$2) // shensxx
reg.exec('shensxx');
// ["shensxx", "shensxx", index: 0, input: "shensxx", groups: undefined]
引用:在同一正則表達式的后部引用前面的子表達式
這是通過在字符\
后面加一位數字或多位數字來實現的,這個數字指定了圓括號的子表達式在正則表達式中的位置。\1,\3
分別表示正則表達式中的第一個圓括號的子表達式和第三個子表達式(注意這里指的是自表達式中匹配的文本的引用,并不是指子表達式)
注意: 因為子表達式可以嵌套另一個子表達式,所以他們的位置是參與技術的左括號的位置
var a = /([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/; // \2 則是代表的([Ss]sript)
var b = /(['"])[^'"]*\1/; // 只能匹配單引號或者雙引號是成對出現的,不允許出現一個單引號一個雙引號
指定匹配位置
有一些正則表達式匹配的是字符串之間的位置,而不是實際的字符。例如\b
匹配一個單詞邊界,即位于\w
和\W
之間的邊界,或者是一個字符串開始或結束的位置。像\b
這樣放任元素不匹配某個可見的字符,他們指定匹配發生的合法位置。還有一些錨元素^ $
, 分表表示匹配字符串開始和結束的位置。
\b
匹配一個單詞邊界,即位于 \w 和 \W
之間的邊界,或者是一個字符串開始或結束的位置。
var reg = /xx\b/;
reg.test('sxx shen') // true
reg.test('xxs shen') // false
var reg = /\bsx/;
reg.test('sxx shen') // true
reg.test('xsx shen') // false
var reg = /\b\d+/;
reg.test('.123') // true
reg.test('sxx124') // false
\B
匹配非單詞邊界。er\B
能匹配 verb
中的 er
,但不能匹配 never
中的 er
。
x(?=y)
:正向先行斷言,匹配 x
僅僅當 x
后面不跟著 y
x(?!y)
:負向先行斷言, 匹配 x
僅僅當x
后面不跟著 y
我們看下面的正則表達式,意思就是非單詞邊界后面跟這三個數字字符串,而且三個數字字符串后面不再跟數字
var reg = /\B(?=(\d{3})+(?!\d))/g;
'123456789.123456'.replace(reg, ',') // "123,456,789.123,456"
(?<=y)x
:正向后行斷言,匹配 x
僅僅當x
前面跟著 y
var reg = /(?<=95|98|NT|2000)Windows/;
'3.1Windows'.replace(reg, 'aaaa'); // '3.1Windows'沒有匹配到
'2000Windows'.replace(reg, 'aaaa'); // '2000aaaa'
(?<!y)x
:負向后行斷言,匹配 x
僅僅當 x
前面跟的不是 y
var reg = /(?<!95|98|NT|2000)Windows/;
'3.1Windows'.replace(reg, 'aaaa'); // '3.1aaaa'
'2000Windows'.replace(reg, 'aaaa'); // '2000Windows' 沒有匹配到
看個demo,不在小數點后加千分符
var reg = /(?<!\.\d*)\B(?=(\d{3})+(?!\d))/g
'123456789.98764525437'.replace(reg, ',')
// "123,456,789.98764525437"
修飾符
i: 執行時不區分大小寫
g: 執行一個全局匹配,簡而言之,就是找到所有的匹配,而不是在找到第一個之后就停止
m: 多行匹配模式,^
匹配一行的開頭和字符串的開頭,$
匹配行的結束和字符串的結束
使用字面量形式時: /\bjavascript\b/ig
構造函數形式: new RegExp('\bjavascript\b', 'ig')
用于模式匹配的String方法,string支持4種使用正則表達式的方式
search() 不支持全局匹配,會忽略修飾符g
@params: 一個正則表達式,如果參數不是一個正則表達式,那么會通過 `RegExp` 構造函數轉成正則表達式
@return: 第一個與之匹配的字符串的起始位置,若是沒有發生匹配就返回數字 `-1`
'fsfsdjavsssjavasdfsfsjfjhhshh3r98u'.search(a) // 11
var a = /[Jj]ava([Ss]crit)*/g;
'012345javascript 67890'.search(a) // 6
replace()
@params: RegExp | string (正則表達式,可以設置修飾符)
@params: function | string
@return: 返回一個新的字符串,不改變源對象
如果第一個參數是一個string,那么 replace()
將直接搜索這個字符串然后進行替換(注意這里指替換第一個搜索到的結果),并不會先轉成 RegExp
進行匹配。
如果第一個參數是一個 RegExp
,那么 replace()
將會進行正則匹配,將第一次匹配到結果進行替換,這里正則表達式可以設置修飾符。g
會進行全局多次匹配,將所有匹配到的結果進行替換
如果第二個參數是一個 string
,那么 replace()
將匹配的結果直接用這個字符串替換
如果第二個參數是一個 function
,我們看看可以有哪些參數:
@params: 完整模式匹配到的結果
@params: 完整模式下的子模式匹配到的結果,看正則表達式中有多少個圓括號,那么這里就可以有多少個這樣的參數,每個參數表示對應的子模式匹配的結果
@return: 使用 return
的返回值替換匹配到的值
var a = /([Jj]ava([Ss]cript))\sis\s(fun\w*)/g;
var b = 'ffhfhjavascript is functionsdfffsff';
b.replace(a, function(match) {
// 打印的結果就是 javascript is functionsdfffsff 123
console.log(match, 123);
return '@@@@'
});
// ffhfh@@@@
b.replace(a, function(match, v1, v2, v3) {
// 打印的結果就是 javascript is functionsdfffsff 123
console.log(match, 123);
// javascript
console.log(v1);
// script
console.log(v2);
// functionsdfffsff
console.log(v3);
return '@@@@'
});
// ffhfh@@@@
match()
@params: 正則表達式 | string(通過 `RegExp` 的構造函數轉化成正則表達式)
@return: 數組,
如果這個正則表達式沒有設置修飾符 g,match() 就不會進行全局檢索,只檢索第一個匹配。在這種情況下:數組的第一個元素就是就是完整模式匹配的結果,其余的元素則是正則表達式中圓括號的子表達式匹配的結果(如果沒有圓括號就不會有這些元素)。index 表示匹配的位置,input 表示目標字符串
var a = /([Jj]ava([Ss]cript))\sis\s(fun\w*)/;
var b = 'ffhfhjavascript is functionsdfffsff';
b.match(a)
// ["javascript is functionsdfffsff", "javascript", "script", "functionsdfffsff", index: 5, input: "ffhfhjavascript is functionsdfffsff", groups: undefined]
如果添加了修飾符g
,那么返回的數組中就是多次全局模式匹配的結果
var a = /123(sxx)456(hello)/g;
var b ='123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello';
b.match(a)
// ["123sxx456hello", "123sxx456hello", "123sxx456hello"]
RegExp的兩種方法:exec() 和 test()
exec()
@params: string
@return: 數組 | null
如果匹配到結果就返回一個數組,結果和字符串的 match()
方法的非全局匹配返回的結果一樣。但是有一點和 match()
不一樣,就是不管式全局還是非全局都是一樣的結構。如果沒有匹配到結果就返回 null
var a = /123(sxx)456(hello)/g;
var b ='123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello';
a.exec(b);
// [“123sxx456hello", "sxx", "hello", index: 0, input: "123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello", groups: undefined]
test()
@params: string
@return: boolean
這個方法很簡單,匹配成功就返回true
,否則就返回false
參考資料
JavaScript權威指南
https://juejin.im/post/5d0c5f47e51d4550bf1ae87b