粘性标志 "y",在位置处搜索
前言
正则表达式中的y 标志允许在源字符串中的指定位置执行搜索。
为了掌握 y 标志的用例,看看它有多好,让我们来探讨一个实际的用例。
regexps 的常见任务之一是"词法分析":比如我们在程序设计语言中得到一个文本,然后分析它的结构元素。
例如,HTML 有标签和属性,JavaScript 代码有函数、变量等。
编写词法分析器是一个特殊的领域,有自己的工具和算法,所以我们就不深究了,但有一个共同的任务:在给定的位置读出一些东西。
例如,我们有一个代码字符串 let varName = "value",我们需要从其中读取变量名,这个变量名从位置 4 开始。
我们用 regexp \w+ 来查找变量名。实际上,JavaScript 的变量名需要更复杂的 regexp 来进行准确的匹配,但在这里并不重要。
调用 str.match(/\w+/) 将只找到该行中的第一个单词。或者是所有带标记 g 的单词。但我们只需要在位置 4 的一个词。
要从给定位置搜索,我们可以使用方法 regexp.exec(str)。
如果 regexp 没有标志 g 或 y,那么这个方法就可以寻找字符串 str 中的第一个匹配,就像 str.match(regexp) 一样。这种简单的无标志的情况我们在这里并不感兴趣。
如果有标志 g,那么它就会在字符串 str 中执行搜索,从存储在 regexp.lastIndex 属性中的位置开始。如果发现匹配,则将 regexp.lastIndex 设置为匹配后的索引。
当一个 regexp 被创建时,它的 lastIndex 是 0。
因此,连续调用 regexp.exec(str) 会一个接一个地返回匹配。
例子
例子1(不用标志g)
let str = 'let varName';
let regexp = /\w+/;
// 正则的lastIndex默认为0
console.log(regexp.lastIndex); // 0
// 不带g标志的exec和match方法返回的结果一样,返回第一个匹配的结果
console.log(regexp.exec(str)); // [ 'let', index: 0, input: 'let varName', groups: undefined ]
// 正则的lastIndex不会改变,扔然是0
console.log(regexp.lastIndex); // 0例子2(用标志 g ):
let str = 'let varName';
let regexp = /\w+/g;
// 带g标志的正则每次执行完一次exec方法后,如果有新的返回结果,就会更改lastIndex的值,初始时默认为0
console.log(regexp.lastIndex); // 0(最初 lastIndex=0)
let word1 = regexp.exec(str);
console.log(word1); // [ 'let', index: 0, input: 'let varName', groups: undefined ]
console.log(word1[0]); // let(第一个单词)
console.log(regexp.lastIndex); // 3(匹配后的位置)
let word2 = regexp.exec(str);
console.log(word2); // [ 'varName', index: 4, input: 'let varName', groups: undefined ]
console.log(word2[0]); // varName (第二个单词)
console.log(regexp.lastIndex); // 11(匹配后的位置)
let word3 = regexp.exec(str);
console.log(word3); // null(没有更多的匹配)
console.log(regexp.lastIndex); // 0(搜索结束时重置)每个匹配都会以数组形式返回,包含分组和附加属性。
我们可以在循环中得到所有的匹配。
let str = 'let varName';
let regexp = /\w+/g;
let result;
while (result = regexp.exec(str)) {
console.log(`Found ${result[0]} at position ${result.index}`);
// 在位置 0 发现 let, 然后
// 在位置 4 发现 varName
}
/*
Found let at position 0
Found varName at position 4
*/``regexp.exec()与标记y`
regexp.exec()
regexp.exec 是 str.matchAll 方法的替代方法。
与其他方法不同,我们可以设置自己的 lastIndex,从给定位置开始搜索。
例如,让我们从位置 4 开始寻找一个单词。
let str = 'let varName = "value"';
let regexp = /\w+/g; // 如果没有标志 "g",属性 lastIndex 会被忽略
// 上来就可以设置从第几位开始查找
regexp.lastIndex = 4;
let word = regexp.exec(str);
console.log(word); // varName
/*
[
'varName',
index: 4,
input: 'let varName = "value"',
groups: undefined
]
*/我们从位置 regexp.lastIndex = 4 开始搜索 w+。
请注意:搜索从位置 lastIndex 开始,然后再往前走。如果在 lastIndex 位置上没有词,但它在后面的某个地方,那么它就会被找到:
let str = 'let varName = "value"';
let regexp = /\w+/g;
regexp.lastIndex = 3;
let word = regexp.exec(str);
console.log(word[0]); // varName
console.log(word.index); // 4……所以,用标志 g 属性 lastIndex 设置搜索的起始位置。
标记y
标记 y 使 regexp.exec 正好在 lastIndex 位置,而不是在它之前,也不是在它之后。
下面是使用标志 y 进行同样的搜索。
let str = 'let varName = "value"';
let regexp = /\w+/y;
regexp.lastIndex = 3;
console.log(regexp.exec(str)); // null(位置 3 有一个空格,不是单词)
regexp.lastIndex = 4;
console.log(regexp.exec(str)); // varName(在位置 4 的单词)
/*
[
'varName',
index: 4,
input: 'let varName = "value"',
groups: undefined
]
*/我们可以看到,regexp /\w+/y 在位置 3 处不匹配(不同于标志 g ),而是在位置 4 处匹配。
想象一下,我们有一个长的文本,而里面根本没有匹配。那么用标志 g 搜索将一直到文本的最后,这将比用标志 y 搜索要花费更多的时间。
在像词法分析这样的任务中,通常在一个确切的位置会有很多搜索。使用标志 y 是获得良好性能的关键。
总结
当想要在指定位置处搜索一个字符串时,可以对正则设置lastIndex属性,但是有以下注意点:
正则默认的
lastIndex属性为0jslet str = `let name = "cheny" ` let reg = /\w+/ console.log(reg.lastIndex) // 0 console.log(str.match(reg)); // [ 'let', index: 0, input: 'let name = "cheny" ', groups: undefined ]想要在执行位置处搜索,需搭配
reg.exec()方法当正则不带标志
g时,reg.exec()方法和str.match()方法,表现一致,且正则的lastIndex属性会被忽略jslet str = `let name = "cheny" ` let reg = /\w+/ // 如果正则表达式不带标志g时,reg.exec()方法和str.match()方法表现一致 console.log(reg.lastIndex) // 0 console.log(reg.exec(str)) // [ 'let', index: 0, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 0jslet str = `let name = "cheny" ` let reg = /\w+/ reg.lastIndex = 4 // 手动设置开始查找的位置,但是因为没有标记 g,并不会生效 console.log(reg.lastIndex) // 4 // 如果正则表达式不带标志g时,reg.exec()方法和str.match()方法表现一致 // 因为没有带 标记 g, 设置的lastIndex并没有生效,仍然是从索引0开始查找的 console.log(reg.exec(str)) // [ 'let', index: 0, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 4 console.log(reg.exec(str)) // [ 'let', index: 0, input: 'let name = "cheny" ', groups: undefined ]当带标志
g时,就可以在指定位置处搜索了jslet str = `let name = "cheny" ` let reg = /\w+/g console.log(reg.lastIndex) // 0 默认lastIndex为 0,也可以手动设置 console.log(reg.exec(str)) // [ 'let', index: 0, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 3 console.log(reg.exec(str)) // [ 'name', index: 4, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 8 console.log(reg.exec(str)) // [ 'cheny', index: 12, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 17 console.log(reg.exec(str)) // null console.log(reg.lastIndex) // 0 返回null之后,lastIndex就又重新赋值为0了jslet str = `let name = "cheny" ` let reg = /\w+/g // 如果不带标记 g lastIndex会被自动忽略 // 手动设置lastIndex 设置从哪里开始查找 reg.lastIndex = 1 console.log(reg.lastIndex) // 1 所以被设置成1了,所以从1开始查找 console.log(reg.exec(str)) // [ 'et', index: 1, input: 'let name = "cheny" ', groups: undefined ]标记
g,会默认查找所有字符串,但是当我们只想查找一次,并且也想让lastIndex生效时(当不带g时,lastIndex会被自动忽略),所以这时候就可以使用标记y来代替,标记y,可以在指定索引处查找一次。jslet str = `let name = "cheny" ` let reg = /\w+/y // 如果不带标记 g lastIndex会被自动忽略 // 但是标记 g 会默认匹配全部的字符串,但是我们不想把所有文本都匹配一遍,只想从设置的位置处查找一次,所以 g 标记就不满足需求了 // 于是就出现了 标记 y ,可以在指定索引处查找一次 // 手动设置lastIndex 设置从哪里开始查找 reg.lastIndex = 3 console.log(reg.lastIndex) // 3 初始查找索引被设置成3了,所以从3开始查找 console.log(reg.exec(str)) // null 索引3的位置是空格,没有匹配到\d规则,y 标记不会继续往下找了 直接就返回null(这就是与 g 标记的区别,如果是g标记会继续往下找) console.log(reg.lastIndex) // 0 返回null之后,lastIndex就又重新赋值为0了jslet str = `let name = "cheny" ` let reg = /\w+/y // 如果不带标记 g lastIndex会被自动忽略 // 但是标记 g 会默认匹配全部的字符串,但是我们不想把所有文本都匹配一遍,只想从设置的位置处查找一次,所以 g 标记就不满足需求了 // 于是就出现了 标记 y ,可以在指定索引处查找一次 // 手动设置lastIndex 设置从哪里开始查找 reg.lastIndex = 4 console.log(reg.lastIndex) // 4 初始查找索引被设置成4了,所以从4开始查找 console.log(reg.exec(str)) // [ 'name', index: 4, input: 'let name = "cheny" ', groups: undefined ] console.log(reg.lastIndex) // 8 console.log(reg.exec(str)) // null 索引8 的位置是空格,不满足规则,所以返回null console.log(reg.lastIndex) // 返回null之后,lastIndex就又重新赋值为0了