如何 clone 一個正則?
克隆一個正則,Lodash 庫的實現方式是:
const reFlags = /\w*$/ function cloneRegExp(regexp) { const result = new regexp.constructor(regexp.source, reFlags.exec(regexp)) result.lastIndex = regexp.lastIndex return result } cloneRegExp(/xyz/gim) // => /xyz/gim 複製程式碼
通過這段程式碼,我們順便複習一下JS
正則物件的部分知識。
1.建構函式
首先,regexp.constructor
就是RegExp
。
瞭解JS
原型相關知識的話,這一點應該沒問題。
具體說來,/xyz/gim
是正則字面量,是建構函式RegExp
的例項。/xyz/gim
取constructor
屬性時,根據原型鏈原理,物件本身沒有此屬性時,要再去它的原型裡找。而/xyz/gim
的原型是RegExp.prototype
。同時RegExp.prototype.constructor
正是RegExp
本身。
建構函式RexExp
的一個典型用法是:
var regexp = new RegExp('xyz', 'gim'); // 等價於 var regexp = /xyz/gim; 複製程式碼
2.正則例項組成
一個正則物件可以大致分成兩部分,原始碼(source) 和修飾符(flags)。比如,/xyz/gim
的source
是"xyz"
,而其flags
是"gim"
。
var regexp = /xyz/gim regexp.source // => "xyz" regexp.flags // => "gim" 複製程式碼
關於修飾符,多說一句。在JS
中,目前共有6
個修飾符:g
、i
、m
、s
、u
、y
。正則物件轉化為字串時,其修飾符排序是按字母排序的。
var regexp = /xyz/imgyus; regexp.flags // => "gimsuy" regexp.toString() // => "/xyz/gimsuy" 複製程式碼
Lodash 的原始碼,獲取修飾符用時沒有通過flags
,而是採用正則提取:
/\w*$/.exec(regexp.toString()).toString() // => gim 複製程式碼
其中,正則/\w*$/
匹配的是字串尾部字母。因為目標正則可能沒有修飾符,因此這裡量詞是*
。
估計你看出來了。是的,下面程式碼裡有兩處型別轉換(轉字串):
new regexp.constructor(regexp.source, reFlags.exec(regexp)) 複製程式碼
3.lastIndex是可修改的
clone 正則時,還要 clone 其lastIndex
。這一點學到了!
lastIndex
表示每次匹配時的開始位置。
使用正則物件的test
和exec
方法,而且當修飾符為g
或y
時, 對lastIndex
是有影響的。
例如:
var regexp = /\d/g; regexp.lastIndex // => 0 regexp.test("123") // => true regexp.lastIndex // => 1 regexp.test("1") // => false 複製程式碼
第1
次test
時,在輸入字串"123"
中匹配到了第一個數字"1"
。lastIndex
此時也變成了1
,表示下次的匹配位置將會跳過第0
位,直接從第1
位開始。
第2
次test
時,此時輸入是字串"1"
,只有一位字元,其第1
位是空,因此匹配失敗。此時lastIndex
會重置為0
。
最關鍵一點,lastIndex
屬性不僅可讀,而且可寫:
var regexp = /\d/g; regexp.lastIndex = 3 regexp.test("123") // => false 複製程式碼
至此,lodash 的實現,應該都能全部看懂了:
const reFlags = /\w*$/ function cloneRegExp(regexp) { const result = new regexp.constructor(regexp.source, reFlags.exec(regexp)) result.lastIndex = regexp.lastIndex return result } cloneRegExp(/xyz/gim) // => /xyz/gim 複製程式碼
本文完。
歡迎閱讀《JS正則迷你書》。
本文參考: