起因
起因是一個朋友問怎么實現一個密碼檢查功能:
- 密碼只能由大寫字母,小寫字母,數字構成;
- 密碼不能以數字開頭;
- 密碼中至少出現大寫字母,小寫字母和數字這三種字符類型中的兩種;
- 密碼長度8-100位
然后他貼了寫的代碼:
$value = 'A1234567890a';
$rule = '/^[A-Z][A-Za-z]{7,100}|^[A-Z][A-Z0-9]{7,100}|^[a-z][A-Za-z]{7,100}|^[a-z][0-9a-z]{7,100}$/';
var_dump(preg_match($rule,(string)$value));
一看這變量名以$
開頭,大概是php的,但我不怎么懂PHP,只看得懂中間的正則,猜最后一行是輸出匹配結果。
這個正則顯然是不滿足上面說的條件的,|
的優先級比$
低,所以前面的表達式沒有控制密碼長度的作用。我依稀記得曾經學正則也看過相關的題目,這種復雜的正則檢查是要用零寬斷言。
然而我覺得零寬斷言這東西寫起來復雜,別人看更覺得復雜的東西,團隊里面顯然不是所有人都會去弄懂的。寫這種復雜正則,以后需求變更,修改起來簡直是噩夢,所以沒怎么認真看,只知道有這么個東西,於是我就認真翻了一下相關的文章。
鏈接如下:
https://deerchao.net/tutorials/regex/regex.htm
這個是我覺得寫得比較好的,內容很充實的一個文章了。
解決問題
對於這個需求,我們先完成第1
、2
和4
點,這三個比較簡單,正則寫出來如下
var regex = /^[A-Za-z][A-Za-z0-9]{7,99}$/
這個問題難寫的就是第3
點,如果不用零寬斷言,是要用很多個|
去做判斷,非常長。
第三點是
密碼中至少出現大寫字母,小寫字母和數字這三種字符類型中的兩種;
換個說法就是
不能全是大寫字母,不能全是小寫字母,不能全是數字。
或者說
存在一個非大寫字母,存在一個非小寫字母,存在一個非數字。
第一種
對於第一種說法,可以用
var regex = /^(?![A-Z]*$)/; // 不能全是大寫字母
var regex2 = /^(?![a-z]*$)/; // 不能全是小寫字母
var regex3 = /^(?![0-9]*$)/; // 不能全是數字
這里的*
可以換成+
,只會在空字符串的時候有差異,對於我們上面的需求是沒有差異的,因為限定了8-100的長度。
與前面的正則組合起來就是
var regex = /^(?![A-Z]*$)(?![a-z]*$)(?![0-9]*$)[A-Za-z][A-Za-z0-9]{7,99}$/;
第二種
對於第二種說法,可以用
var regex = /^(?=.*[^A-Z])/ // 存在一個非大寫字母
var regex2 = /^(?=.*[^a-z])/ // 存在一個非小寫字母
var regex3 = /^(?=.*[^0-9])/ // 存在一個非數字
這里*
不能替換成+
,為什么可以自己思考一下,我會給一反例證明替換有問題。
組合起來就是
var regex = /^(?=.*[^A-Z])(?=.*[^a-z])(?=.*[^0-9])[A-Za-z][A-Za-z0-9]{7,99}$/;
如果將所有的*
替換成+
,那么對於Aaaaaaaa
,正則匹配會失敗,根據需求應該是成功。
第三種
什么?居然還有第三種?是的,當然還有第三種,和第四種。
剛剛我們寫的斷言,按照鏈接給的名字是零寬度正預測先行斷言(?=exp)
和零寬度負預測先行斷言(?!exp)
,當然名字其實不重要的,翻譯過來的名字有很多種,不同的文章也不同,也不知道哪個是比較官方的。
所以我們可以用另外兩種斷言實現,分辨是零寬度正回顧后發斷言(?<=exp)
和零寬度負回顧后發斷言(?<!exp)
對於第三種和第四種,它是從后面檢查前面的斷言,這種斷言據說
js是不支持的,但是chrome的引擎似乎是支持的,也不清楚是怎么回事。
用(?<!exp)
形式寫第一種說法
var regex = /(?<!^[A-Z]*)$/; // 不能全是大寫字母
var regex2 = /(?<!^[a-z]*)$/; // 不能全是小寫字母
var regex3 = /(?<!^[0-9]*)$/; // 不能全是數字
同樣的*
可以替換成+
,組合起來就是
var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<!^[A-Z]*)(?<!^[a-z]*)(?<!^[0-9]*)$/
第四種
不廢話了直接貼代碼
var regex = /(?<=[^A-Z].*)$/ // 存在一個非大寫字母
var regex2 = /(?<=[^a-z].*)$/ // 存在一個非小寫字母
var regex3 = /(?<=[^0-9].*)$/ // 存在一個非數字
組合后如下,同樣的不能把*
替換成+
,反例是aaaaaaaA
var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<=[^A-Z].*)(?<=[^a-z].*)(?<=[^0-9].*)$/;
第五種、第六種……
組合有很多種,因為條件都說的很清楚了,四種斷言是可以組合的,所以其實遠不止以上說的四種情況,重要的是要掌握四種斷言的本質。
結尾
本文只是一個引子,並不是說怎么學正則,學習正則可以參考上面給的鏈接,這里再給一次https://deerchao.net/tutorials/regex/regex.htm
警告:不要寫過於復雜的正則,多人協作中,代碼的可讀性遠比性能和炫技重要。