克隆一个正则,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正则迷你书》。
本文参考: