今天偶然在國外的 Web 社群中看到一個很有趣的問題。問題的原意大概是:我宣告了一個 Regex 並用迴圈去跑,但發現輸出的結果是不同的,程式碼如下:
let arr = ['2','5','3'];
let drgx = /\d/g;
arr.forEach((i,ind) => {
console.log('index: '+ind+' result: '+drgx.test(i))
})
如果有學過 Regex 應該都知道,\d 代表的是吻合數字(如果不知道也沒關係,因為我也很常忘記)g 則是代表 global,我第一次看到這段程式碼,直覺就是:輸出 3 個 true。實際上跑的結果:
今天這篇文就想來記錄一下為什麼會發生這樣的狀況。
RegExp.prototype.test()
Regex.test 讓我們可以運用一段 Regex 去看 test 中所帶的字串是否匹配 Regex 並回傳布林值,例如:
/luke/.test('luke') // true
/leia/.test('luke') // false
global
在 Regex 中,我們在 /reg/ 的最後方放上 g 代表 global search。當我們使用 String.match(Regex) 時,在不使用 global 的狀況下,在匹配到第一個符合的字串時就會回傳;global 則是會回傳所有匹配的字串,例如:
var r = /a\d/g
var r2 = /a\d/
var str = 'a1a2a3'
str.match(r) // ["a1", "a2", "a3"]
str.match(r2) // ["a1", index: 0, input: "a1a2a3", groups: undefined]
RegExp.lastIndex
當我們使用 global search 搭配 Regex.test 或 Regex.exec 時,這些方法會增加 Regex 的 lastIndex 至匹配條件字串的下一個索引,直到其方法回傳 falsy 的值(test 回傳 false,exec 回傳 null)便會將其索引重設為 0。以 test 來續用上面的例子:
r.test(str) // true
r.lastIndex // 2
r.test(str) // true
r.lastIndex // 4
r.test(str) // true
r.lastIndex // 6
r.test(str) // false
r.lastIndex // 0
再跑第二次時,r 就會從字串的第二個索引開始找,因為匹配到了 a2 ,所以會回傳 true,再跑第三次時會從第四個索引開始找,因為匹配到了 a3,所以會回傳 true,到了第六個索引,因為沒東西了,所以回傳 false 並將 lastIndex 重設為 0,由於又從頭找起,因此下一次又會回傳 true。我們用 exec 再看一次:
回到一開始提到的例子:
let arr = ['2','5','3'];
let drgx = /\d/g;
arr.forEach((i,ind) => {
console.log('index: '+ind+' result: '+drgx.test(i))
})
因為在 forEach 時,drgx 的 lastIndex 不斷在 0 和 1 中切換,所以才會有一下 true 一下 false 的狀況發生。解決的辦法有兩個,一個是我們在每次 forEach 時都將 lastIndex 重設為 0:
let arr = ['2','5','3'];
let drgx = /\d/g;
arr.forEach((i,ind) => {
drgx.lastIndex = 0
console.log('index: '+ind+' result: '+drgx.test(i))
})
// index: 0 result: true
// index: 1 result: true
// index: 2 result: true
另一個方法則是將 drgx 宣在 forEach 中切開作用域:
let arr = ['2','5','3'];
arr.forEach((i,ind) => {
let drgx = /\d/g;
console.log('index: '+ind+' result: '+drgx.test(i))
})
// index: 0 result: true
// index: 1 result: true
// index: 2 result: true
當然,最好的方法就是不要使用 global。至少在該網友的程式碼中,使用 global 看起來是沒有必要的。
針對 Regex.test 搭配 global 會碰到的問題就記錄到這,我自己在使用時基本上都只會在 function 中使用一次,因此剛看到時也滿頭問號以為過年過太爽把 Regex 都還給 Google 了。如果內文有任何錯誤,還請大大們指出,謝謝,祝大家牛年行大運!
Reference: