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