JavaScript正则表达式中g标志详解

目录
  • 缘起
  • 解密过程
    • 搜索引擎
    • 源码层面
  • 结论

缘起

有一天在思否社区看到有个问题,大致描述如下

const list = ['a', 'b', '-', 'c', 'd'];
const reg = /[a-z]/g;
const letters = list.filter(i => reg.test(i));

// letters === ['a', 'c'];
// 如果正则不使用`g`标志可以得到所有的字母
// 为什么加入`g`之后就不可以了

对问题而言,遍历中的i就是一个字符,不需要用到g。

但是就我对正则的理解(过于浅薄)感觉上有没有g(只是全局搜索,不会匹配到就停下来)应该不影响,激发了我的好奇心。

上面题的建议写法如下

const reg = /[a-z]/g;
reg.test('a'); // => true
reg.test('a'); // => false
reg.test('a'); // => true
reg.test('a'); // => false
reg.test('a'); // => true

解密过程

首先可以确定的表现一定是g导致的

搜索引擎

打开 MDN 仔细查看g标志的作用,得到结论和我的理解无二。

我猜想应该就是g可能启用了某种缓存,又因为reg相对过滤器是全局变量,我将代码改为:

const list = ['a', 'b', '-', 'c', 'd'];
const letters = list.filter(i => /[a-z]/g.test(i));

// letters === ['a', 'b', 'c', 'd'];

将正则声明到每一次遍历,得到结论就是正确的,验证了我的猜想。也得到了,缓存就是正则中的某个地方

下面我找到对应的源码来查看问题的原因

源码层面

由于最近在看 Rust,所以使用 Rust 编写的源码查看

打开项目后,点击.进入 vscode 模式,command+p 搜索 regexp 关键词

进入test.rs文件,command+f 搜索/g可以找到在 90 行有个last_index()的测试

#[test]
fn last_index() {
    let mut context = Context::default();
    let init = r#"
        var regex = /[0-9]+(\.[0-9]+)?/g;
        "#;
    // forward 的作用:更改 context,并返回结果的字符串。
    eprintln!("{}", forward(&mut context, init));
    assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
    assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "true");
    assert_eq!(forward(&mut context, "regex.lastIndex"), "3");
    assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "false");
    assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
}

看到了有lastIndex关键字,这里再已经大致猜到问题的原因了,g 标志存在匹配后的最后一个下标,导致出现问题。

我们将视线移入到mod.rs文件中,搜索test

在 631 行看到了fn test()方法

pub(crate) fn test(
    this: &JsValue,
    args: &[JsValue],
    context: &mut Context,
) -> JsResult<JsValue> {
    // 1. Let R be the this value.
    // 2. If Type(R) is not Object, throw a TypeError exception.
    let this = this.as_object().ok_or_else(|| {
        context
            .construct_type_error("RegExp.prototype.test method called on incompatible value")
    })?;

    // 3. Let string be ? ToString(S).
    let arg_str = args
        .get(0)
        .cloned()
        .unwrap_or_default()
        .to_string(context)?;

    // 4. Let match be ? RegExpExec(R, string).
    let m = Self::abstract_exec(this, arg_str, context)?;

    // 5. If match is not null, return true; else return false.
    if m.is_some() {
        Ok(JsValue::new(true))
    } else {
        Ok(JsValue::new(false))
    }
}

test()方法中找到了Self::abstract_exec()方法

pub(crate) fn abstract_exec(
    this: &JsObject,
    input: JsString,
    context: &mut Context,
) -> JsResult<Option<JsObject>> {
    // 1. Assert: Type(R) is Object.
    // 2. Assert: Type(S) is String.

    // 3. Let exec be ? Get(R, "exec").
    let exec = this.get("exec", context)?;

    // 4. If IsCallable(exec) is true, then
    if let Some(exec) = exec.as_callable() {
        // a. Let result be ? Call(exec, R, « S »).
        let result = exec.call(&this.clone().into(), &[input.into()], context)?;

        // b. If Type(result) is neither Object nor Null, throw a TypeError exception.
        if !result.is_object() && !result.is_null() {
            return context.throw_type_error("regexp exec returned neither object nor null");
        }

        // c. Return result.
        return Ok(result.as_object().cloned());
    }

    // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]).
    if !this.is_regexp() {
        return context.throw_type_error("RegExpExec called with invalid value");
    }

    // 6. Return ? RegExpBuiltinExec(R, S).
    Self::abstract_builtin_exec(this, &input, context)
}

又在Self::abstract_exec()方法中找到了Self::abstract_builtin_exec()方法

pub(crate) fn abstract_builtin_exec(
    this: &JsObject,
    input: &JsString,
    context: &mut Context,
) -> JsResult<Option<JsObject>> {
    // 1. Assert: R is an initialized RegExp instance.
    let rx = {
        let obj = this.borrow();
        if let Some(rx) = obj.as_regexp() {
            rx.clone()
        } else {
            return context.throw_type_error("RegExpBuiltinExec called with invalid value");
        }
    };

    // 2. Assert: Type(S) is String.

    // 3. Let length be the number of code units in S.
    let length = input.encode_utf16().count();

    // 4. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
    let mut last_index = this.get("lastIndex", context)?.to_length(context)?;

    // 5. Let flags be R.[[OriginalFlags]].
    let flags = &rx.original_flags;

    // 6. If flags contains "g", let global be true; else let global be false.
    let global = flags.contains('g');

    // 7. If flags contains "y", let sticky be true; else let sticky be false.
    let sticky = flags.contains('y');

    // 8. If global is false and sticky is false, set lastIndex to 0.
    if !global && !sticky {
        last_index = 0;
    }

    // 9. Let matcher be R.[[RegExpMatcher]].
    let matcher = &rx.matcher;

    // 10. If flags contains "u", let fullUnicode be true; else let fullUnicode be false.
    let unicode = flags.contains('u');

    // 11. Let matchSucceeded be false.
    // 12. Repeat, while matchSucceeded is false,
    let match_value = loop {
        // a. If lastIndex > length, then
        if last_index > length {
            // i. If global is true or sticky is true, then
            if global || sticky {
                // 1. Perform ? Set(R, "lastIndex", +0                        
(0)

相关推荐

  • 正确使用带有"g"标记的javascript正则表达式

    下面我们要谈谈带有"g" 标记的正则表达式的使用问题,首先让我们看一段代码,从例子入手更容易理解. 复制代码 代码如下: 1 function a(val) 2 { 3 var re = /^\d+$/g; 4 alert(re.lastIndex); 5 return re.test(val); 6 } 7 alert(a(5)); 8 alert(a(6)); 运行上述代码,在不同浏览器中会得到不同结果,IE中:0 true 0 true,FF和Chrome中:0 true 1 f

  • JS中使用正则表达式g模式和非g模式的区别

    先给大家说下js正则表达式中的g到底是什么意思 g是global的缩写啊! 就是匹配全部可匹配结果, 如果你不带g,在正则过程中,字符串是从左至右匹配的,如果匹配成功就不再继续向右匹配了,如果你带g,它会重头到尾的把正确匹配的字符串挑选出来 例如: var str = 'aaaaaaaa' var reg1 = /a/ var reg2 = /a/g str.match(reg1) // 结果为:["a", index: 0, input: "aaaaaaaa"]

  • JS正则表达式修饰符global(/g)用法分析

    本文实例讲述了JS正则表达式修饰符global(/g)用法.分享给大家供大家参考,具体如下: /g修饰符代表全局匹配,查找所有匹配而非在找到第一个匹配后停止.先看一段经典代码: var str = "123#abc"; var noglobal = /abc/i;//非全局匹配模式 console.log(re.test(str)); //输出ture console.log(re.test(str)); //输出ture console.log(re.test(str)); //输出

  • javascript正则表达式参数/g与/i及/gi的使用指南

    regularexpression=/pattern/[switch] 这个switch就有三种值 g: 全局匹配 i: 忽略大小写 gi: 全局匹配 + 忽略大小写 JScript 语言参考 -------------------------------------------------------------------------------- 后向引用 正则表达式一个最重要的特性就是将匹配成功的模式的某部分进行存储供以后使用这一能力.请回想一下,对一个正则表达式模式或部分模式两边添加圆

  • JS中正则表达式全局匹配模式 /g用法详解

    本文章来详细介绍js中正则表达式的全局匹配模式 /g用法,代码如下: var str = "123#abc"; var re = /abc/ig; console.log(re.test(str)); //输出ture console.log(re.test(str)); //输出false console.log(re.test(str)); //输出ture console.log(re.test(str)); //输出false 在创建正则表达式对象时如果使用了"g&q

  • JavaScript正则表达式中g标志详解

    目录 缘起 解密过程 搜索引擎 源码层面 结论 缘起 有一天在思否社区看到有个问题,大致描述如下 const list = ['a', 'b', '-', 'c', 'd']; const reg = /[a-z]/g; const letters = list.filter(i => reg.test(i)); // letters === ['a', 'c']; // 如果正则不使用`g`标志可以得到所有的字母 // 为什么加入`g`之后就不可以了 对问题而言,遍历中的i就是一个字符,不需要

  • javascript 正则表达式分组、断言详解

     javascript 正则表达式分组.断言详解 提示:阅读本文需要有一定的正则表达式基础. 正则表达式中的断言,作为高级应用出现,倒不是因为它有多难,而是概念比较抽象,不容易理解而已,今天就让小菜通俗的讲解一下. 如果不用断言,以往用过的那些表达式,仅仅能获取到有规律的字符串,而不能获取无规律的字符串. 举个例子,比如html源码中有<title>xxx</title>标签,用以前的知识,我们只能确定源码中的<title>和</title>是固定不变的.因

  • 常用JavaScript正则表达式汇编与示例详解

    1.1 前言 目前收集整理了21个常用的javaScript正则表达式,其中包括用户名.密码强度.整数.数字.电子邮件地址(Email).手机号码.身份证号.URL地址. IP地址. 十六进制颜色. 日期. 微信号.车牌号.中文正则等.表单验证处理必备,赶紧收藏吧! 还会陆续加入新的正则进来,大家多提宝贵意见! 2.1 用户名正则 2.1.1 基本用户名正则 在做用户注册时,都会用到用户名正则校验. 定义基本用户名命名规则如下: 最短4位,最长16位 {4,16} 可以包含小写大母 [a-z]

  • JavaScript正则表达式的分组匹配详解

    分组 下面的正则表达式可以匹配kidkidkid: /kidkidkid/ 而另一种更优雅的写法是: /(kid){3}/ 这里由圆括号包裹的一个小整体称为分组. 候选 一个分组中,可以有多个候选表达式,用|分隔: var reg = /I love (him|her|it)/; reg.test('I love him') // true reg.test('I love her') // true reg.test('I love it') // true reg.test('I love

  • javascript正则表达式中分组详解

    之前写了一篇关于正则新手入门的文章,本以为对正则表达式相对比较了解 但是今天我又遇到了一个坑,可能是自己不够细心的原因吧,今天就着重和大家分享一下javascript正则表达式中的分组.如果你对JS正则表达式不够理解 可以点击这里了解更多. 分组在正则中用的还是比较广的,我所理解的分组 就是一对括号() ,每一对括号 就代表了一个分组, 分组可以分为: 捕获性分组 非捕获性分组 捕获性分组 捕获性分组会在 比如 match exec这样的函数中以第二项,第三项的形式得到相应分组的结果.先来看一个

  • C++中正则表达式的使用方法详解

    目录 介绍 1. C++ 中的正则表达式 (Regex) 1.1 范围规范 1.2 重复模式 2. C++正则表达式的例子 3. C++正则表达式中使用的函数模板 3.1 regex_match() 3.2 regex_search() 3.3 regex_replace() 4.C++输入验证 5.总结 介绍 C++ 正则表达式教程解释了 C++ 中正则表达式的工作,包括正则表达式匹配.搜索.替换.输入验证和标记化的功能. 几乎所有的编程语言都支持正则表达式. C++ 从 C++11 开始直接

  • Java中正则表达式的使用和详解(下)

    在上篇给大家介绍了Java中正则表达式的使用和详解(上),具体内容如下所示: 1.常用正则表达式 规则 正则表达式语法   一个或多个汉字 ^[\u0391-\uFFE5]+$  邮政编码 ^[1-9]\d{5}$ QQ号码 ^[1-9]\d{4,10}$  邮箱 ^[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\.){1,3}[a-zA-z\-]{1,}$  用户名(字母开头 + 数字/字母/下划线) ^[A-Za-z][A-Za-z1-9_-]+$ 手

  • Java中正则表达式的使用和详解(上)

    1.匹配验证-验证Email是否正确 public static void main(String[] args) { // 要验证的字符串 String str = "service@xsoftlab.net"; // 邮箱验证规则 String regEx = "[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\\.){1,3}[a-zA-z\\-]{1,}"; // 编译正则表达式 Pattern pattern = Pa

  • Jmeter中正则表达式提取器使用详解

    目录 在使用Jmeter过程中,会经常使用到正则表达式提取器提取器,虽然并不直接涉及到请求的测试,但是对于数据的传递起着很大的作用,本篇博文就是主要讲解关于正则表达式及其在Jmeter的Sampler中的调用. 一.首先来介绍一下Jmeter中正则表达式提取器的元素组成,下图所示: 下面是各参数值的含义: 参数 释义 引用名称 在HTTP等请求中,引用此数据,需要用到的名称 正则表达式 用于将需要的数据提取出来 模板 表示使用提取到的第几个值: $1$:表示取第1个 $2$:表示取第二个 以此类

随机推荐