克隆一個正則,Lodash 庫的實現方式是:javascript
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
正則對象的部分知識。java
首先,regexp.constructor
就是 RegExp
。git
瞭解 JS
原型相關知識的話,這一點應該沒問題。github
具體說來,/xyz/gim
是正則字面量,是構造函數 RegExp
的實例。/xyz/gim
取 constructor
屬性時,根據原型鏈原理,對象自己沒有此屬性時,要再去它的原型裏找。而 /xyz/gim
的原型是 RegExp.prototype
。同時 RegExp.prototype.constructor
正是 RegExp
自己。markdown
構造函數 RexExp
的一個典型用法是:函數
var regexp = new RegExp('xyz', 'gim'); // 等價於 var regexp = /xyz/gim; 複製代碼
一個正則對象能夠大體分紅兩部分,源碼(source) 和修飾符(flags)。好比,/xyz/gim
的 source
是 "xyz"
,而其 flags
是 "gim"
。oop
var regexp = /xyz/gim regexp.source // => "xyz" regexp.flags // => "gim" 複製代碼
關於修飾符,多說一句。在 JS
中,目前共有 6
個修飾符:g
、i
、m
、s
、u
、y
。正則對象轉化爲字符串時,其修飾符排序是按字母排序的。post
var regexp = /xyz/imgyus; regexp.flags // => "gimsuy" regexp.toString() // => "/xyz/gimsuy" 複製代碼
Lodash 的源碼,獲取修飾符用時沒有經過 flags
,而是採用正則提取:spa
/\w*$/.exec(regexp.toString()).toString() // => gim 複製代碼
其中,正則 /\w*$/
匹配的是字符串尾部字母。由於目標正則可能沒有修飾符,所以這裏量詞是 *
。prototype
估計你看出來了。是的,下面代碼裏有兩處類型轉換(轉字符串):
new regexp.constructor(regexp.source, reFlags.exec(regexp)) 複製代碼
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正則迷你書》。
本文參考: